diff --git a/.gitignore b/.gitignore index c8de817..516aa9f 100644 --- a/.gitignore +++ b/.gitignore @@ -364,3 +364,5 @@ MigrationBackup/ FodyWeavers.xsd /Api/appsettings.json /Api/appsettings.Development.json +/Ui/src/environments/environment.development.ts +/Ui/src/environments/environment.ts diff --git a/Api/.config/dotnet-tools.json b/Api/.config/dotnet-tools.json new file mode 100644 index 0000000..4f48799 --- /dev/null +++ b/Api/.config/dotnet-tools.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-ef": { + "version": "9.0.0", + "commands": [ + "dotnet-ef" + ], + "rollForward": false + } + } +} \ No newline at end of file diff --git a/Api/Api.csproj b/Api/Api.csproj index fdb1a36..553510d 100644 --- a/Api/Api.csproj +++ b/Api/Api.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0 enable enable @@ -10,15 +10,16 @@ - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + diff --git a/Api/Controllers/v1/AdmonitionsController.cs b/Api/Controllers/v1/AdmonitionsController.cs index 0d61022..178415b 100644 --- a/Api/Controllers/v1/AdmonitionsController.cs +++ b/Api/Controllers/v1/AdmonitionsController.cs @@ -10,8 +10,8 @@ namespace Api.Controllers.v1 [Route("api/v{version:apiVersion}/[controller]")] [ApiController] [ApiVersion("1.0")] - //[Authorize] - public class AdmonitionsController(IAdmonitionRepository admonitionRepository, ILogger logger) : ControllerBase + [Authorize] + public class AdmonitionsController(IAdmonitionRepository admonitionRepository, IClaimTypeService claimTypeService, ILogger logger) : ControllerBase { [HttpGet] public async Task>> GetAdmonitions(CancellationToken cancellationToken) @@ -80,7 +80,7 @@ namespace Api.Controllers.v1 if (!ModelState.IsValid) return UnprocessableEntity(ModelState); var createResult = - await admonitionRepository.CreateAdmonitionAsync(createAdmonitionDto, cancellationToken); + await admonitionRepository.CreateAdmonitionAsync(createAdmonitionDto, claimTypeService.GetFullName(User), cancellationToken); return createResult.IsFailure ? BadRequest(createResult.Error) @@ -104,7 +104,7 @@ namespace Api.Controllers.v1 if (admonitionId != updateAdmonitionDto.Id) return Conflict(AdmonitionErrors.IdConflict); var updateResult = - await admonitionRepository.UpdateAdmonitionAsync(updateAdmonitionDto, cancellationToken); + await admonitionRepository.UpdateAdmonitionAsync(updateAdmonitionDto, claimTypeService.GetFullName(User), cancellationToken); return updateResult.IsFailure ? BadRequest(updateResult.Error) diff --git a/Api/Controllers/v1/AlliancesController.cs b/Api/Controllers/v1/AlliancesController.cs index 4597d13..5d72a94 100644 --- a/Api/Controllers/v1/AlliancesController.cs +++ b/Api/Controllers/v1/AlliancesController.cs @@ -11,7 +11,7 @@ namespace Api.Controllers.v1 [ApiController] [ApiVersion("1.0")] [Authorize] - public class AlliancesController(IAllianceRepository allianceRepository, ILogger logger) : ControllerBase + public class AlliancesController(IAllianceRepository allianceRepository, IClaimTypeService claimTypeService, ILogger logger) : ControllerBase { [HttpGet] public async Task>> GetAlliances(CancellationToken cancellationToken) @@ -51,28 +51,6 @@ namespace Api.Controllers.v1 } } - - [HttpPost] - public async Task> CreateAlliance(CreateAllianceDto createAllianceDto, - CancellationToken cancellationToken) - { - try - { - if (!ModelState.IsValid) return UnprocessableEntity(ModelState); - - var createResult = await allianceRepository.CreateAllianceAsync(createAllianceDto, cancellationToken); - - return createResult.IsFailure - ? BadRequest(createResult.Error) - : CreatedAtAction(nameof(GetAlliance), new { allianceId = createResult.Value.Id }, createResult.Value); - } - catch (Exception e) - { - logger.LogError(e, e.Message); - return StatusCode(StatusCodes.Status500InternalServerError); - } - } - [HttpPut("{allianceId:guid}")] public async Task> UpdateAlliance(Guid allianceId, UpdateAllianceDto updateAllianceDto, CancellationToken cancellationToken) { @@ -82,7 +60,7 @@ namespace Api.Controllers.v1 if (allianceId != updateAllianceDto.Id) return Conflict(AllianceErrors.IdConflict); - var updateResult = await allianceRepository.UpdateAllianceAsync(updateAllianceDto, cancellationToken); + var updateResult = await allianceRepository.UpdateAllianceAsync(updateAllianceDto, claimTypeService.GetFullName(User), cancellationToken); return updateResult.IsFailure ? BadRequest(updateResult.Error) diff --git a/Api/Controllers/v1/AuthenticationsController.cs b/Api/Controllers/v1/AuthenticationsController.cs index 803263a..51dd60c 100644 --- a/Api/Controllers/v1/AuthenticationsController.cs +++ b/Api/Controllers/v1/AuthenticationsController.cs @@ -14,18 +14,41 @@ namespace Api.Controllers.v1 { [AllowAnonymous] [HttpPost("[action]")] - public async Task> Register(RegisterRequestDto registerRequestDto, CancellationToken cancellationToken) + public async Task> SignUp(SignUpRequestDto signUpRequestDto, CancellationToken cancellationToken) { try { if (!ModelState.IsValid) return UnprocessableEntity(ModelState); var registerResult = - await authenticationRepository.RegisterToApplicationAsync(registerRequestDto, cancellationToken); + await authenticationRepository.RegisterToApplicationAsync(signUpRequestDto, cancellationToken); return registerResult.IsFailure ? BadRequest(registerResult.Error) - : Ok(registerResult.Value); + : Ok(true); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [AllowAnonymous] + [HttpPost("[action]")] + public async Task> RegisterUser(RegisterUserDto registerUserDto, + CancellationToken cancellationToken) + { + try + { + if (!ModelState.IsValid) return UnprocessableEntity(ModelState); + + var registerResult = + await authenticationRepository.RegisterUserAsync(registerUserDto, cancellationToken); + + return registerResult.IsFailure + ? BadRequest(registerResult.Error) + : Ok(true); } catch (Exception e) { @@ -54,5 +77,120 @@ namespace Api.Controllers.v1 return StatusCode(StatusCodes.Status500InternalServerError); } } + + [AllowAnonymous] + [HttpPost("[action]")] + public async Task> ResendConfirmationEmail( + EmailConfirmationRequestDto emailConfirmationRequestDto, CancellationToken cancellationToken) + { + try + { + if (!ModelState.IsValid) return UnprocessableEntity(ModelState); + + var resendConfirmationEmailResult = + await authenticationRepository.ResendConfirmationEmailAsync(emailConfirmationRequestDto); + + return resendConfirmationEmailResult.IsFailure + ? BadRequest(resendConfirmationEmailResult.Error) + : Ok(true); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [AllowAnonymous] + [HttpPost("[action]")] + public async Task> ConfirmEmail(ConfirmEmailRequestDto confirmEmailRequestDto, CancellationToken cancellationToken) + { + try + { + if (!ModelState.IsValid) return UnprocessableEntity(ModelState); + + var resendConfirmationEmailResult = + await authenticationRepository.EmailConfirmationAsync(confirmEmailRequestDto); + + return resendConfirmationEmailResult.IsFailure + ? BadRequest(resendConfirmationEmailResult.Error) + : Ok(true); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPost("[action]")] + public async Task> InviteUser(InviteUserDto inviteUserDto, + CancellationToken cancellationToken) + { + try + { + if (!ModelState.IsValid) return UnprocessableEntity(ModelState); + + var inviteUserResult = await authenticationRepository.InviteUserAsync(inviteUserDto, cancellationToken); + + if (inviteUserResult.IsFailure) return BadRequest(inviteUserResult.Error); + + return Ok(true); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [AllowAnonymous] + [HttpPost("[action]")] + public async Task> ForgotPassword(ForgotPasswordDto forgotPasswordDto, + CancellationToken cancellationToken) + { + try + { + if (!ModelState.IsValid) return UnprocessableEntity(ModelState); + + var forgotPasswordResponse = await authenticationRepository.ForgotPasswordAsync(forgotPasswordDto); + + if (forgotPasswordResponse.IsFailure) + { + logger.LogWarning(forgotPasswordResponse.Error.Name); + } + return Ok(true); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [AllowAnonymous] + [HttpPost("[action]")] + public async Task> ResetPassword(ResetPasswordDto resetPasswordDto, + CancellationToken cancellationToken) + { + try + { + if (!ModelState.IsValid) return UnprocessableEntity(ModelState); + + if (resetPasswordDto.Password != resetPasswordDto.ConfirmPassword) + return BadRequest("Reset password failed"); + + var resetPasswordResult = await authenticationRepository.ResetPasswordAsync(resetPasswordDto); + + return resetPasswordResult.IsFailure + ? BadRequest(resetPasswordResult.Error) + : Ok(true); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } } } diff --git a/Api/Controllers/v1/CustomEventsController.cs b/Api/Controllers/v1/CustomEventsController.cs new file mode 100644 index 0000000..4655570 --- /dev/null +++ b/Api/Controllers/v1/CustomEventsController.cs @@ -0,0 +1,143 @@ +using Application.DataTransferObjects.CustomEvent; +using Application.Errors; +using Application.Interfaces; +using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Api.Controllers.v1 +{ + [Route("api/v{version:apiVersion}/[controller]")] + [ApiController] + [ApiVersion("1.0")] + [Authorize] + public class CustomEventsController(ICustomEventRepository customEventRepository, IClaimTypeService claimTypeService, ILogger logger) : ControllerBase + { + [HttpGet("{customEventId:guid}")] + public async Task> GetCustomEvent(Guid customEventId, + CancellationToken cancellationToken) + { + try + { + var customEventResult = + await customEventRepository.GetCustomEventAsync(customEventId, cancellationToken); + + return customEventResult.IsFailure + ? BadRequest(customEventResult.Error) + : Ok(customEventResult.Value); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("Alliance/{allianceId:guid}")] + public async Task>> GetAllianceCustomEvents(Guid allianceId, + [FromQuery] int take, CancellationToken cancellationToken) + { + try + { + var allianceCustomEventsResult = + await customEventRepository.GetAllianceCustomEventsAsync(allianceId, take, cancellationToken); + + if (allianceCustomEventsResult.IsFailure) return BadRequest(allianceCustomEventsResult.Error); + + return allianceCustomEventsResult.Value.Count > 0 + ? Ok(allianceCustomEventsResult.Value) + : NoContent(); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("[action]/{customEventId:guid}")] + public async Task> GetCustomEventDetail(Guid customEventId, + CancellationToken cancellationToken) + { + try + { + var customEventDetailResult = + await customEventRepository.GetCustomEventDetailAsync(customEventId, cancellationToken); + + return customEventDetailResult.IsFailure + ? BadRequest(customEventDetailResult.Error) + : Ok(customEventDetailResult.Value); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPost] + public async Task> CreateCustomEvent(CreateCustomEventDto createCustomEventDto, + CancellationToken cancellationToken) + { + try + { + if (!ModelState.IsValid) return UnprocessableEntity(ModelState); + + var createResult = await customEventRepository.CreateCustomEventAsync(createCustomEventDto, + claimTypeService.GetFullName(User), cancellationToken); + + return createResult.IsFailure + ? BadRequest(createResult.Error) + : CreatedAtAction(nameof(GetCustomEvent), new { customEventId = createResult.Value.Id }, + createResult.Value); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPut("{customEventId:guid}")] + public async Task> UpdateCustomEvent(Guid customEventId, + UpdateCustomEventDto updateCustomEventDto, CancellationToken cancellationToken) + { + try + { + if (!ModelState.IsValid) return UnprocessableEntity(ModelState); + + if (updateCustomEventDto.Id != customEventId) return Conflict(CustomEventErrors.IdConflict); + + var updateResult = await customEventRepository.UpdateCustomEventAsync(updateCustomEventDto, + claimTypeService.GetFullName(User), cancellationToken); + + return updateResult.IsFailure + ? BadRequest(updateResult.Error) + : Ok(updateResult.Value); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpDelete("{customEventId:guid}")] + public async Task> DeleteCustomEvent(Guid customEventId, CancellationToken cancellationToken) + { + try + { + var deleteResult = await customEventRepository.DeleteCustomEventAsync(customEventId, cancellationToken); + + return deleteResult.IsFailure + ? BadRequest(deleteResult.Error) + : Ok(deleteResult.Value); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + } +} diff --git a/Api/Controllers/v1/DesertStormParticipantsController.cs b/Api/Controllers/v1/DesertStormParticipantsController.cs new file mode 100644 index 0000000..c2b1c7c --- /dev/null +++ b/Api/Controllers/v1/DesertStormParticipantsController.cs @@ -0,0 +1,111 @@ +using Application.DataTransferObjects.DesertStormParticipants; +using Application.Interfaces; +using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Api.Controllers.v1 +{ + [Route("api/v{version:apiVersion}/[controller]")] + [ApiController] + [ApiVersion("1.0")] + [Authorize] + public class DesertStormParticipantsController(IDesertStormParticipantRepository desertStormParticipantRepository, ILogger logger) : ControllerBase + { + [HttpGet("{desertStormParticipantId:guid}")] + public async Task> GetDesertStormParticipant( + Guid desertStormParticipantId, CancellationToken cancellationToken) + { + try + { + var desertStormParticipantResult = + await desertStormParticipantRepository.GetDesertStormParticipantAsync(desertStormParticipantId, + cancellationToken); + + return desertStormParticipantResult.IsFailure + ? BadRequest(desertStormParticipantResult.Error) + : Ok(desertStormParticipantResult.Value); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("Player/{playerId:guid}")] + public async Task>> GetPlayerDesertStormParticipants( + Guid playerId, [FromQuery] int last, + CancellationToken cancellationToken) + { + try + { + var desertStormPlayerParticipatedResult = + await desertStormParticipantRepository.GetPlayerDesertStormParticipantsAsync(playerId, last, + cancellationToken); + + if (desertStormPlayerParticipatedResult.IsFailure) + return BadRequest(desertStormPlayerParticipatedResult.Error); + + return desertStormPlayerParticipatedResult.Value.Count > 0 + ? Ok(desertStormPlayerParticipatedResult.Value) + : NoContent(); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPost] + public async Task InsertDesertStormParticipant( + List createDesertStormParticipantsDto, CancellationToken cancellationToken) + { + try + { + if (!ModelState.IsValid) return UnprocessableEntity(ModelState); + + var createResult = + await desertStormParticipantRepository.InsertDesertStormParticipantAsync( + createDesertStormParticipantsDto, cancellationToken); + + return createResult.IsFailure + ? BadRequest(createResult.Error) + : StatusCode(StatusCodes.Status201Created); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPut("{desertStormParticipantId:guid}")] + public async Task> UpdateMarshalGuardParticipant( + Guid desertStormParticipantId, + UpdateDesertStormParticipantDto updateDesertStormParticipantDto, CancellationToken cancellationToken) + { + try + { + if (!ModelState.IsValid) return UnprocessableEntity(ModelState); + + if (desertStormParticipantId != updateDesertStormParticipantDto.Id) + return Conflict(new Error("","")); + + var updateResult = + await desertStormParticipantRepository.UpdateDesertStormParticipantAsync( + updateDesertStormParticipantDto, cancellationToken); + + return updateResult.IsFailure + ? BadRequest(updateResult.Error) + : Ok(updateResult.Value); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + } +} diff --git a/Api/Controllers/v1/DesertStormsController.cs b/Api/Controllers/v1/DesertStormsController.cs index 8a816c5..6ffb902 100644 --- a/Api/Controllers/v1/DesertStormsController.cs +++ b/Api/Controllers/v1/DesertStormsController.cs @@ -2,6 +2,7 @@ using Application.Errors; using Application.Interfaces; using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Api.Controllers.v1 @@ -9,8 +10,8 @@ namespace Api.Controllers.v1 [Route("api/v{version:apiVersion}/[controller]")] [ApiController] [ApiVersion("1.0")] - //[Authorize] - public class DesertStormsController(IDesertStormRepository desertStormRepository, ILogger logger) : ControllerBase + [Authorize] + public class DesertStormsController(IDesertStormRepository desertStormRepository, IClaimTypeService claimTypeService, ILogger logger) : ControllerBase { [HttpGet("{desertStormId:guid}")] public async Task> GetDesertStorm(Guid desertStormId, @@ -32,20 +33,40 @@ namespace Api.Controllers.v1 } } - [HttpGet("Player/{playerId:guid}")] - public async Task>> GetPlayerDesertStorms(Guid playerId, + [HttpGet("Alliance/{allianceId:guid}")] + public async Task>> GetAllianceDesertStorms(Guid allianceId, + [FromQuery] int take, CancellationToken cancellationToken) + { + try + { + var allianceDesertStormsResult = + await desertStormRepository.GetAllianceDesertStormsAsync(allianceId, take, cancellationToken); + + if (allianceDesertStormsResult.IsFailure) return BadRequest(allianceDesertStormsResult.Error); + + return allianceDesertStormsResult.Value.Count > 0 + ? Ok(allianceDesertStormsResult.Value) + : NoContent(); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("[action]/{desertStormId:guid}")] + public async Task> GetDesertStormDetail(Guid desertStormId, CancellationToken cancellationToken) { try { - var playerDesertStormsResult = - await desertStormRepository.GetPlayerDesertStormsAsync(playerId, cancellationToken); + var desertStormDetailResult = + await desertStormRepository.GetDesertStormDetailAsync(desertStormId, cancellationToken); - if (playerDesertStormsResult.IsFailure) return BadRequest(playerDesertStormsResult.Error); - - return playerDesertStormsResult.Value.Count > 0 - ? Ok(playerDesertStormsResult.Value) - : NoContent(); + return desertStormDetailResult.IsFailure + ? BadRequest(desertStormDetailResult.Error) + : Ok(desertStormDetailResult.Value); } catch (Exception e) { @@ -63,7 +84,7 @@ namespace Api.Controllers.v1 if (!ModelState.IsValid) return UnprocessableEntity(ModelState); var createResult = - await desertStormRepository.CreateDesertStormAsync(createDesertStormDto, cancellationToken); + await desertStormRepository.CreateDesertStormAsync(createDesertStormDto, claimTypeService.GetFullName(User), cancellationToken); return createResult.IsFailure ? BadRequest(createResult.Error) @@ -88,7 +109,7 @@ namespace Api.Controllers.v1 if (desertStormId != updateDesertStormDto.Id) return Conflict(DesertStormErrors.IdConflict); var updateResult = - await desertStormRepository.UpdateDesertStormAsync(updateDesertStormDto, cancellationToken); + await desertStormRepository.UpdateDesertStormAsync(updateDesertStormDto, claimTypeService.GetFullName(User), cancellationToken); return updateResult.IsFailure ? BadRequest(updateResult.Error) diff --git a/Api/Controllers/v1/MarshalGuardParticipantsController.cs b/Api/Controllers/v1/MarshalGuardParticipantsController.cs new file mode 100644 index 0000000..a95ee4d --- /dev/null +++ b/Api/Controllers/v1/MarshalGuardParticipantsController.cs @@ -0,0 +1,106 @@ +using Application.DataTransferObjects.MarshalGuardParticipant; +using Application.Errors; +using Application.Interfaces; +using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Api.Controllers.v1 +{ + [Route("api/v{version:apiVersion}/[controller]")] + [ApiController] + [ApiVersion("1.0")] + [Authorize] + public class MarshalGuardParticipantsController(IMarshalGuardParticipantRepository marshalGuardParticipantRepository, ILogger logger) : ControllerBase + { + [HttpGet("{marshalGuardParticipantId:guid}")] + public async Task> GetMarshalGuardParticipant( + Guid marshalGuardParticipantId, CancellationToken cancellationToken) + { + try + { + var marshalGuardParticipantResult = + await marshalGuardParticipantRepository.GetMarshalGuardParticipantAsync(marshalGuardParticipantId, + cancellationToken); + + return marshalGuardParticipantResult.IsFailure + ? BadRequest(marshalGuardParticipantResult.Error) + : Ok(marshalGuardParticipantResult.Value); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("Player/{playerId:guid}")] + public async Task>> GetPlayerMarshalGuardsParticipants(Guid playerId, [FromQuery] int last, + CancellationToken cancellationToken) + { + try + { + var numberOfParticipationResult = + await marshalGuardParticipantRepository.GetPlayerMarshalParticipantsAsync(playerId, last, + cancellationToken); + return numberOfParticipationResult.IsFailure + ? BadRequest(numberOfParticipationResult.Error) + : Ok(numberOfParticipationResult.Value); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPost] + public async Task InsertMarshalGuardParticipant( + List createMarshalGuardParticipantsDto, CancellationToken cancellationToken) + { + try + { + if (!ModelState.IsValid) return UnprocessableEntity(ModelState); + + var createResult = + await marshalGuardParticipantRepository.InsertMarshalGuardParticipantAsync( + createMarshalGuardParticipantsDto, cancellationToken); + + return createResult.IsFailure + ? BadRequest(createResult.Error) + : StatusCode(StatusCodes.Status201Created); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPut("{marshalGuardParticipantId:guid}")] + public async Task> UpdateMarshalGuardParticipant(Guid marshalGuardParticipantId, + UpdateMarshalGuardParticipantDto updateMarshalGuardParticipantDto, CancellationToken cancellationToken) + { + try + { + if (!ModelState.IsValid) return UnprocessableEntity(ModelState); + + if (marshalGuardParticipantId != updateMarshalGuardParticipantDto.Id) + return Conflict(MarshalGuardErrors.IdConflict); + + var updateResult = + await marshalGuardParticipantRepository.UpdateMarshalGuardParticipantAsync( + updateMarshalGuardParticipantDto, cancellationToken); + + return updateResult.IsFailure + ? BadRequest(updateResult.Error) + : Ok(updateResult.Value); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + } +} diff --git a/Api/Controllers/v1/MarshalGuardsController.cs b/Api/Controllers/v1/MarshalGuardsController.cs index bd800d1..17abfd0 100644 --- a/Api/Controllers/v1/MarshalGuardsController.cs +++ b/Api/Controllers/v1/MarshalGuardsController.cs @@ -2,6 +2,7 @@ using Application.Errors; using Application.Interfaces; using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Api.Controllers.v1 @@ -9,8 +10,8 @@ namespace Api.Controllers.v1 [Route("api/v{version:apiVersion}/[controller]")] [ApiController] [ApiVersion("1.0")] - //[Authorize] - public class MarshalGuardsController(IMarshalGuardRepository marshalGuardRepository, ILogger logger) : ControllerBase + [Authorize] + public class MarshalGuardsController(IMarshalGuardRepository marshalGuardRepository, IClaimTypeService claimTypeService, ILogger logger) : ControllerBase { [HttpGet("{marshalGuardId:guid}")] public async Task> GetMarshalGuard(Guid marshalGuardId, @@ -32,19 +33,19 @@ namespace Api.Controllers.v1 } } - [HttpGet("Player/{playerId:guid}")] - public async Task>> GetPlayerMarshalGuards(Guid playerId, + [HttpGet("Alliance/{allianceId:guid}")] + public async Task>> GetAllianceMarshalGuards(Guid allianceId, [FromQuery] int take, CancellationToken cancellationToken) { try { - var playerMarshalGuardsResult = - await marshalGuardRepository.GetPlayerMarshalGuardsAsync(playerId, cancellationToken); + var allianceMarshalGuardsResult = + await marshalGuardRepository.GetAllianceMarshalGuardsAsync(allianceId, take, cancellationToken); - if (playerMarshalGuardsResult.IsFailure) return BadRequest(playerMarshalGuardsResult.Error); + if (allianceMarshalGuardsResult.IsFailure) return BadRequest(allianceMarshalGuardsResult.Error); - return playerMarshalGuardsResult.Value.Count > 0 - ? Ok(playerMarshalGuardsResult.Value) + return allianceMarshalGuardsResult.Value.Count > 0 + ? Ok(allianceMarshalGuardsResult.Value) : NoContent(); } catch (Exception e) @@ -54,6 +55,27 @@ namespace Api.Controllers.v1 } } + [HttpGet("[action]/{marshalGuardId:guid}")] + public async Task> GetMarshalGuardDetail(Guid marshalGuardId, + CancellationToken cancellationToken) + { + try + { + var marshalGuardDetailResult = + await marshalGuardRepository.GetMarshalGuardDetailAsync(marshalGuardId, cancellationToken); + + return marshalGuardDetailResult.IsFailure + ? BadRequest(marshalGuardDetailResult.Error) + : Ok(marshalGuardDetailResult.Value); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPost] public async Task> CreateMarshalGuard(CreateMarshalGuardDto createMarshalGuardDto, CancellationToken cancellationToken) @@ -63,17 +85,17 @@ namespace Api.Controllers.v1 if (!ModelState.IsValid) return UnprocessableEntity(ModelState); var createResult = - await marshalGuardRepository.CreateMarshalGuardAsync(createMarshalGuardDto, cancellationToken); + await marshalGuardRepository.CreateMarshalGuardsAsync(createMarshalGuardDto, claimTypeService.GetFullName(User), cancellationToken); return createResult.IsFailure ? BadRequest(createResult.Error) - : CreatedAtAction(nameof(GetMarshalGuard), new { marshalGuardId = createResult.Value.Id }, - createResult.Value); + : CreatedAtAction(nameof(GetMarshalGuard), + new { marshalGuardId = createResult.Value.Id}, createResult.Value); } catch (Exception e) { logger.LogError(e, e.Message); - return StatusCode(StatusCodes.Status500InternalServerError); ; + return StatusCode(StatusCodes.Status500InternalServerError); } } @@ -83,12 +105,12 @@ namespace Api.Controllers.v1 { try { - if (ModelState.IsValid) return UnprocessableEntity(ModelState); + if (!ModelState.IsValid) return UnprocessableEntity(ModelState); if (marshalGuardId != updateMarshalGuardDto.Id) return Conflict(MarshalGuardErrors.IdConflict); var updateResult = - await marshalGuardRepository.UpdateMarshalGuardAsync(updateMarshalGuardDto, cancellationToken); + await marshalGuardRepository.UpdateMarshalGuardAsync(updateMarshalGuardDto, claimTypeService.GetFullName(User), cancellationToken); return updateResult.IsFailure ? BadRequest(updateResult.Error) diff --git a/Api/Controllers/v1/NotesController.cs b/Api/Controllers/v1/NotesController.cs index 5a3dad9..ef16e6f 100644 --- a/Api/Controllers/v1/NotesController.cs +++ b/Api/Controllers/v1/NotesController.cs @@ -2,6 +2,7 @@ using Application.Errors; using Application.Interfaces; using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Api.Controllers.v1 @@ -9,8 +10,8 @@ namespace Api.Controllers.v1 [Route("api/v{version:apiVersion}/[controller]")] [ApiController] [ApiVersion("1.0")] - //[Authorize] - public class NotesController(INoteRepository noteRepository, ILogger logger) : ControllerBase + [Authorize] + public class NotesController(INoteRepository noteRepository, IClaimTypeService claimTypeService, ILogger logger) : ControllerBase { [HttpGet("{noteId:guid}")] public async Task> GetNote(Guid noteId, CancellationToken cancellationToken) @@ -59,7 +60,7 @@ namespace Api.Controllers.v1 { if (!ModelState.IsValid) return UnprocessableEntity(ModelState); - var createResult = await noteRepository.CreateNoteAsync(createNoteDto, cancellationToken); + var createResult = await noteRepository.CreateNoteAsync(createNoteDto, claimTypeService.GetFullName(User), cancellationToken); return createResult.IsFailure ? BadRequest(createResult.Error) @@ -82,7 +83,7 @@ namespace Api.Controllers.v1 if (noteId != updateNoteDto.Id) return Conflict(NoteErrors.IdConflict); - var updateResult = await noteRepository.UpdateNoteAsync(updateNoteDto, cancellationToken); + var updateResult = await noteRepository.UpdateNoteAsync(updateNoteDto, claimTypeService.GetFullName(User), cancellationToken); return updateResult.IsFailure ? BadRequest(updateResult.Error) diff --git a/Api/Controllers/v1/PlayersController.cs b/Api/Controllers/v1/PlayersController.cs index d8e832e..6487a37 100644 --- a/Api/Controllers/v1/PlayersController.cs +++ b/Api/Controllers/v1/PlayersController.cs @@ -2,6 +2,7 @@ using Application.Errors; using Application.Interfaces; using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -10,8 +11,8 @@ namespace Api.Controllers.v1 [Route("api/v{version:apiVersion}/[controller]")] [ApiController] [ApiVersion("1.0")] - //[Authorize] - public class PlayersController(IPlayerRepository playerRepository, ILogger logger) : ControllerBase + [Authorize] + public class PlayersController(IPlayerRepository playerRepository, IClaimTypeService claimTypeService, ILogger logger) : ControllerBase { [HttpGet("{playerId:guid}")] public async Task> GetPlayer(Guid playerId, CancellationToken cancellationToken) @@ -61,7 +62,7 @@ namespace Api.Controllers.v1 { if (!ModelState.IsValid) return UnprocessableEntity(ModelState); - var createResult = await playerRepository.CreatePlayerAsync(createPlayerDto, cancellationToken); + var createResult = await playerRepository.CreatePlayerAsync(createPlayerDto, claimTypeService.GetFullName(User), cancellationToken); return createResult.IsFailure ? BadRequest(createResult.Error) @@ -83,7 +84,7 @@ namespace Api.Controllers.v1 if (playerId != updatePlayerDto.Id) return Conflict(PlayerErrors.IdConflict); - var updateResult = await playerRepository.UpdatePlayerAsync(updatePlayerDto, cancellationToken); + var updateResult = await playerRepository.UpdatePlayerAsync(updatePlayerDto, claimTypeService.GetFullName(User), cancellationToken); return updateResult.IsFailure ? BadRequest(updateResult.Error) diff --git a/Api/Controllers/v1/RanksController.cs b/Api/Controllers/v1/RanksController.cs new file mode 100644 index 0000000..c4c6385 --- /dev/null +++ b/Api/Controllers/v1/RanksController.cs @@ -0,0 +1,35 @@ +using Application.DataTransferObjects.Rank; +using Application.Interfaces; +using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Api.Controllers.v1 +{ + [Route("api/v{version:apiVersion}/[controller]")] + [ApiController] + [ApiVersion("1.0")] + [Authorize] + public class RanksController(IRankRepository rankRepository, ILogger logger) : ControllerBase + { + [HttpGet] + public async Task>> GetRanks(CancellationToken cancellationToken) + { + try + { + var ranksResult = await rankRepository.GetRanksAsync(cancellationToken); + + if (ranksResult.IsFailure) return BadRequest(ranksResult.Error); + + return ranksResult.Value.Count > 0 + ? Ok(ranksResult.Value) + : NoContent(); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + } +} diff --git a/Api/Controllers/v1/UsersController.cs b/Api/Controllers/v1/UsersController.cs new file mode 100644 index 0000000..36a7b59 --- /dev/null +++ b/Api/Controllers/v1/UsersController.cs @@ -0,0 +1,119 @@ +using Application.DataTransferObjects.User; +using Application.Errors; +using Application.Interfaces; +using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Api.Controllers.v1 +{ + [Route("api/v{version:apiVersion}/[controller]")] + [ApiController] + [ApiVersion("1.0")] + [Authorize] + public class UsersController(IUserRepository userRepository, ILogger logger) : ControllerBase + { + [HttpGet("Alliance/{allianceId:guid}")] + public async Task>> GetAllianceUsers(Guid allianceId, + CancellationToken cancellationToken) + { + try + { + var allianceUsersResult = await userRepository.GetAllianceUsersAsync(allianceId, cancellationToken); + + if (allianceUsersResult.IsFailure) return BadRequest(allianceUsersResult.Error); + + return allianceUsersResult.Value.Count > 0 + ? Ok(allianceUsersResult.Value) + : NoContent(); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("{userId:guid}")] + public async Task> GetUser(Guid userId, CancellationToken cancellationToken) + { + try + { + var userResult = await userRepository.GetUserAsync(userId, cancellationToken); + + return userResult.IsFailure + ? BadRequest(userResult.Error) + : Ok(userResult.Value); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPut("{userId:guid}")] + public async Task> UpdateUser(Guid userId, UpdateUserDto updateUserDto, + CancellationToken cancellationToken) + { + try + { + if (!ModelState.IsValid) return UnprocessableEntity(ModelState); + + if (userId != updateUserDto.Id) return Conflict(UserErrors.IdConflict); + + var updateResult = await userRepository.UpdateUserAsync(updateUserDto, cancellationToken); + + return updateResult.IsFailure + ? BadRequest(updateResult.Error) + : Ok(updateResult.Value); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPost("[action]")] + public async Task> ChangeUserPassword(ChangePasswordDto changePasswordDto, + CancellationToken cancellationToken) + { + try + { + if (changePasswordDto.NewPassword != changePasswordDto.ConfirmPassword) + return BadRequest(UserErrors.ConfirmPasswordNotMatch); + + var changePasswordResult = + await userRepository.ChangeUserPasswordAsync(changePasswordDto, cancellationToken); + + return changePasswordResult.IsFailure + ? BadRequest(changePasswordResult.Error) + : Ok(true); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpDelete("{userId:guid}")] + public async Task> DeleteUser(Guid userId, CancellationToken cancellationToken) + { + try + { + var deleteResult = await userRepository.DeleteUserAsync(userId, cancellationToken); + + return deleteResult.IsFailure + ? BadRequest(deleteResult.Error) + : Ok(true); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + } +} diff --git a/Api/Controllers/v1/VsDuelParticipantsController.cs b/Api/Controllers/v1/VsDuelParticipantsController.cs new file mode 100644 index 0000000..700ee16 --- /dev/null +++ b/Api/Controllers/v1/VsDuelParticipantsController.cs @@ -0,0 +1,40 @@ +using Application.DataTransferObjects.VsDuelParticipant; +using Application.Errors; +using Application.Interfaces; +using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Api.Controllers.v1 +{ + [Route("api/v{version:apiVersion}/[controller]")] + [ApiController] + [ApiVersion("1.0")] + [Authorize] + public class VsDuelParticipantsController(IVsDuelParticipantRepository vsDuelParticipantRepository, ILogger logger) : ControllerBase + { + [HttpPut("{vsDuelParticipantId:guid}")] + public async Task> UpdateVsDuelParticipant(Guid vsDuelParticipantId, VsDuelParticipantDto vsDuelParticipantDto, CancellationToken cancellationToken) + { + try + { + if (!ModelState.IsValid) return UnprocessableEntity(ModelState); + + if (vsDuelParticipantId != vsDuelParticipantDto.Id) return Conflict(VsDuelParticipantErrors.IdConflict); + + var updateResult = + await vsDuelParticipantRepository.UpdateVsDuelParticipant(vsDuelParticipantDto, cancellationToken); + + return updateResult.IsFailure + ? BadRequest(updateResult.Error) + : Ok(updateResult.Value); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + } +} diff --git a/Api/Controllers/v1/VsDuelsController.cs b/Api/Controllers/v1/VsDuelsController.cs index 1344bf7..b1a65dd 100644 --- a/Api/Controllers/v1/VsDuelsController.cs +++ b/Api/Controllers/v1/VsDuelsController.cs @@ -2,6 +2,7 @@ using Application.Errors; using Application.Interfaces; using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Api.Controllers.v1 @@ -9,8 +10,8 @@ namespace Api.Controllers.v1 [Route("api/v{version:apiVersion}/[controller]")] [ApiController] [ApiVersion("1.0")] - //[Authorize] - public class VsDuelsController(IVsDuelRepository vsDuelRepository, ILogger logger) : ControllerBase + [Authorize] + public class VsDuelsController(IVsDuelRepository vsDuelRepository, IClaimTypeService claimTypeService, ILogger logger) : ControllerBase { [HttpGet("{vsDuelId:guid}")] public async Task> GetVsDuel(Guid vsDuelId, CancellationToken cancellationToken) @@ -30,18 +31,19 @@ namespace Api.Controllers.v1 } } - [HttpGet("Player/{playerId:guid}")] - public async Task>> GetPlayerVsDuels(Guid playerId, + [HttpGet("Alliance/{allianceId:guid}")] + public async Task>> GetAllianceVsDuels(Guid allianceId, [FromQuery] int take, CancellationToken cancellationToken) { try { - var playerVsDuelsResult = await vsDuelRepository.GetPlayerVsDuelsAsync(playerId, cancellationToken); + var allianceVsDuelsResult = + await vsDuelRepository.GetAllianceVsDuelsAsync(allianceId, take, cancellationToken); - if (playerVsDuelsResult.IsFailure) return BadRequest(playerVsDuelsResult.Error); + if (allianceVsDuelsResult.IsFailure) return BadRequest(allianceVsDuelsResult.Error); - return playerVsDuelsResult.Value.Count > 0 - ? Ok(playerVsDuelsResult.Value) + return allianceVsDuelsResult.Value.Count > 0 + ? Ok(allianceVsDuelsResult.Value) : NoContent(); } catch (Exception e) @@ -51,6 +53,25 @@ namespace Api.Controllers.v1 } } + [HttpGet("[action]/{vsDuelId:guid}")] + public async Task> GetDetailVsDuel(Guid vsDuelId, CancellationToken cancellationToken) + { + try + { + var vsDuelDetailResult = await vsDuelRepository.GetVsDuelDetailAsync(vsDuelId, cancellationToken); + + return vsDuelDetailResult.IsFailure + ? BadRequest(vsDuelDetailResult.Error) + : Ok(vsDuelDetailResult.Value); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPost] public async Task> CreateVsDuel(CreateVsDuelDto createVsDuelDto, CancellationToken cancellationToken) @@ -59,7 +80,7 @@ namespace Api.Controllers.v1 { if (!ModelState.IsValid) return UnprocessableEntity(ModelState); - var createResult = await vsDuelRepository.CreateVsDuelAsync(createVsDuelDto, cancellationToken); + var createResult = await vsDuelRepository.CreateVsDuelAsync(createVsDuelDto, claimTypeService.GetFullName(User), cancellationToken); return createResult.IsFailure ? BadRequest(createResult.Error) @@ -82,7 +103,7 @@ namespace Api.Controllers.v1 if (vsDuelId != updateVsDuelDto.Id) return Conflict(VsDuelErrors.IdConflict); - var updateResult = await vsDuelRepository.UpdateVsDuelAsync(updateVsDuelDto, cancellationToken); + var updateResult = await vsDuelRepository.UpdateVsDuelAsync(updateVsDuelDto, claimTypeService.GetFullName(User), cancellationToken); return updateResult.IsFailure ? BadRequest(updateResult.Error) diff --git a/Api/Program.cs b/Api/Program.cs index 147cee7..17e1900 100644 --- a/Api/Program.cs +++ b/Api/Program.cs @@ -6,6 +6,7 @@ using Database; using HealthChecks.UI.Client; using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Serilog; +using Utilities.Classes; Log.Logger = new LoggerConfiguration() .WriteTo.Console() @@ -32,11 +33,18 @@ try builder.Services.ConfigureAndAddCors(); builder.Services.ConfigureAndAddHealthChecks(builder.Configuration); + builder.Services.AddOptions() + .BindConfiguration("EmailSettings") + .ValidateDataAnnotations() + .ValidateOnStart(); + var jwtSection = builder.Configuration.GetRequiredSection("Jwt"); builder.Services.ConfigureAndAddAuthentication(jwtSection); var app = builder.Build(); + app.UseStaticFiles(); + app.UseDefaultFiles(); app.UseSwagger(); app.UseSwaggerUI(); @@ -57,6 +65,8 @@ try ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse }); + app.MapFallbackToFile("index.html"); + app.Run(); } catch (Exception e) diff --git a/Application/Application.csproj b/Application/Application.csproj index d4f425b..e65ee66 100644 --- a/Application/Application.csproj +++ b/Application/Application.csproj @@ -1,7 +1,7 @@  - net8.0 + net9.0 enable enable @@ -9,12 +9,15 @@ + + + - + diff --git a/Application/ApplicationDependencyInjection.cs b/Application/ApplicationDependencyInjection.cs index 8b30227..9521342 100644 --- a/Application/ApplicationDependencyInjection.cs +++ b/Application/ApplicationDependencyInjection.cs @@ -3,6 +3,8 @@ using Application.Interfaces; using Application.Repositories; using Application.Services; using Microsoft.Extensions.DependencyInjection; +using Utilities.Interfaces; +using Utilities.Services; namespace Application; @@ -20,8 +22,17 @@ public static class ApplicationDependencyInjection services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); return services; } diff --git a/Application/Classes/EmailContent.cs b/Application/Classes/EmailContent.cs new file mode 100644 index 0000000..32d42f6 --- /dev/null +++ b/Application/Classes/EmailContent.cs @@ -0,0 +1,8 @@ +namespace Application.Classes; + +public class EmailContent +{ + public required string Subject { get; set; } + + public required string Content { get; set; } +} \ No newline at end of file diff --git a/Application/DataTransferObjects/Admonition/AdmonitionDto.cs b/Application/DataTransferObjects/Admonition/AdmonitionDto.cs index bfa6ffd..bb67beb 100644 --- a/Application/DataTransferObjects/Admonition/AdmonitionDto.cs +++ b/Application/DataTransferObjects/Admonition/AdmonitionDto.cs @@ -6,5 +6,13 @@ public class AdmonitionDto public required string Reason { get; set; } + public DateTime CreatedOn { get; set; } + + public required string CreatedBy { get; set; } + public Guid PlayerId { get; set; } + + public DateTime? ModifiedOn { get; set; } + + public string? ModifiedBy { get; set; } } \ No newline at end of file diff --git a/Application/DataTransferObjects/Admonition/UpdateAdmonitionDto.cs b/Application/DataTransferObjects/Admonition/UpdateAdmonitionDto.cs index 0e68ac1..1823fb2 100644 --- a/Application/DataTransferObjects/Admonition/UpdateAdmonitionDto.cs +++ b/Application/DataTransferObjects/Admonition/UpdateAdmonitionDto.cs @@ -11,4 +11,7 @@ public class UpdateAdmonitionDto [Required] [MaxLength(250)] public required string Reason { get; set; } + + [Required] + public Guid PlayerId { get; set; } } \ No newline at end of file diff --git a/Application/DataTransferObjects/Alliance/AllianceDto.cs b/Application/DataTransferObjects/Alliance/AllianceDto.cs index bb9bcd8..4e3a632 100644 --- a/Application/DataTransferObjects/Alliance/AllianceDto.cs +++ b/Application/DataTransferObjects/Alliance/AllianceDto.cs @@ -1,4 +1,6 @@ -namespace Application.DataTransferObjects.Alliance; +using System.Dynamic; + +namespace Application.DataTransferObjects.Alliance; public class AllianceDto { @@ -9,4 +11,10 @@ public class AllianceDto public required string Name { get; set; } public required string Abbreviation { get; set; } + + public DateTime CreatedOn { get; set; } + + public DateTime? ModifiedOn { get; set; } + + public string? ModifiedBy { get; set; } } \ No newline at end of file diff --git a/Application/DataTransferObjects/Alliance/CreateAllianceDto.cs b/Application/DataTransferObjects/Alliance/CreateAllianceDto.cs deleted file mode 100644 index af1bed1..0000000 --- a/Application/DataTransferObjects/Alliance/CreateAllianceDto.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Application.DataTransferObjects.Alliance; - -public class CreateAllianceDto -{ - [Required] - public int Server { get; set; } - - [Required] - [MaxLength(200)] - public required string Name { get; set; } - - [Required] - [MaxLength(5)] - public required string Abbreviation { get; set; } -} \ No newline at end of file diff --git a/Application/DataTransferObjects/Alliance/UpdateAllianceDto.cs b/Application/DataTransferObjects/Alliance/UpdateAllianceDto.cs index 42386dc..b7cd609 100644 --- a/Application/DataTransferObjects/Alliance/UpdateAllianceDto.cs +++ b/Application/DataTransferObjects/Alliance/UpdateAllianceDto.cs @@ -13,4 +13,7 @@ public class UpdateAllianceDto [Required] [MaxLength(5)] public required string Abbreviation { get; set; } + + [Required] + public int Server { get; set; } } \ No newline at end of file diff --git a/Application/DataTransferObjects/Authentication/ConfirmEmailRequestDto.cs b/Application/DataTransferObjects/Authentication/ConfirmEmailRequestDto.cs new file mode 100644 index 0000000..c2b9c86 --- /dev/null +++ b/Application/DataTransferObjects/Authentication/ConfirmEmailRequestDto.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.DataAnnotations; + +namespace Application.DataTransferObjects.Authentication; + +public class ConfirmEmailRequestDto +{ + [Required] + [EmailAddress] + public required string Email { get; set; } + + [Required] + public required string Token { get; set; } +} \ No newline at end of file diff --git a/Application/DataTransferObjects/Authentication/EmailConfirmationRequestDto.cs b/Application/DataTransferObjects/Authentication/EmailConfirmationRequestDto.cs new file mode 100644 index 0000000..072f243 --- /dev/null +++ b/Application/DataTransferObjects/Authentication/EmailConfirmationRequestDto.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.DataAnnotations; + +namespace Application.DataTransferObjects.Authentication; + +public class EmailConfirmationRequestDto +{ + [Required] + [EmailAddress] + public required string Email { get; set; } + + [Required] + public required string ClientUri { get; set; } +} \ No newline at end of file diff --git a/Application/DataTransferObjects/Authentication/ForgotPasswordDto.cs b/Application/DataTransferObjects/Authentication/ForgotPasswordDto.cs new file mode 100644 index 0000000..b5a8fbc --- /dev/null +++ b/Application/DataTransferObjects/Authentication/ForgotPasswordDto.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.DataAnnotations; + +namespace Application.DataTransferObjects.Authentication; + +public class ForgotPasswordDto +{ + [Required] + [EmailAddress] + public required string Email { get; set; } + + [Required] + public required string ResetPasswordUri { get; set; } +} \ No newline at end of file diff --git a/Application/DataTransferObjects/Authentication/InviteUserDto.cs b/Application/DataTransferObjects/Authentication/InviteUserDto.cs new file mode 100644 index 0000000..eff1cd6 --- /dev/null +++ b/Application/DataTransferObjects/Authentication/InviteUserDto.cs @@ -0,0 +1,23 @@ +using System.ComponentModel.DataAnnotations; + +namespace Application.DataTransferObjects.Authentication; + +public class InviteUserDto +{ + [Required] + [EmailAddress] + public required string Email { get; set; } + + [Required] + public Guid InvitingUserId { get; set; } + + [Required] + public Guid AllianceId { get; set; } + + [Required] + public required string Role { get; set; } + + [Required] + public required string RegisterUserUri { get; set; } + +} \ No newline at end of file diff --git a/Application/DataTransferObjects/Authentication/RegisterUserDto.cs b/Application/DataTransferObjects/Authentication/RegisterUserDto.cs new file mode 100644 index 0000000..bf8af56 --- /dev/null +++ b/Application/DataTransferObjects/Authentication/RegisterUserDto.cs @@ -0,0 +1,26 @@ +using System.ComponentModel.DataAnnotations; + +namespace Application.DataTransferObjects.Authentication; + +public class RegisterUserDto +{ + [Required] + [EmailAddress] + public required string Email { get; set; } + + [Required] + [MaxLength(200)] + public required string PlayerName { get; set; } + + [Required] + public required string Password { get; set; } + + [Required] + public Guid AllianceId { get; set; } + + [Required] + public Guid RoleId { get; set; } + + [Required] + public required string EmailConfirmUri { get; set; } +} \ No newline at end of file diff --git a/Application/DataTransferObjects/Authentication/ResetPasswordDto.cs b/Application/DataTransferObjects/Authentication/ResetPasswordDto.cs new file mode 100644 index 0000000..08fd9a4 --- /dev/null +++ b/Application/DataTransferObjects/Authentication/ResetPasswordDto.cs @@ -0,0 +1,19 @@ +using System.ComponentModel.DataAnnotations; + +namespace Application.DataTransferObjects.Authentication; + +public class ResetPasswordDto +{ + [Required] + public required string Password { get; set; } + + [Compare("Password")] + public required string ConfirmPassword { get; set; } + + [Required] + [EmailAddress] + public required string Email { get; set; } + + [Required] + public required string Token { get; set; } +} \ No newline at end of file diff --git a/Application/DataTransferObjects/Authentication/RegisterRequestDto.cs b/Application/DataTransferObjects/Authentication/SignUpRequestDto.cs similarity index 85% rename from Application/DataTransferObjects/Authentication/RegisterRequestDto.cs rename to Application/DataTransferObjects/Authentication/SignUpRequestDto.cs index 843d549..6cb9adc 100644 --- a/Application/DataTransferObjects/Authentication/RegisterRequestDto.cs +++ b/Application/DataTransferObjects/Authentication/SignUpRequestDto.cs @@ -2,7 +2,7 @@ namespace Application.DataTransferObjects.Authentication; -public class RegisterRequestDto +public class SignUpRequestDto { [Required] [EmailAddress] @@ -25,4 +25,7 @@ public class RegisterRequestDto [Required] [MaxLength(5)] public required string AllianceAbbreviation { get; set; } + + [Required] + public required string EmailConfirmUri { get; set; } } \ No newline at end of file diff --git a/Application/DataTransferObjects/CustomEvent/CreateCustomEventDto.cs b/Application/DataTransferObjects/CustomEvent/CreateCustomEventDto.cs new file mode 100644 index 0000000..e138acc --- /dev/null +++ b/Application/DataTransferObjects/CustomEvent/CreateCustomEventDto.cs @@ -0,0 +1,26 @@ +using System.ComponentModel.DataAnnotations; + +namespace Application.DataTransferObjects.CustomEvent; + +public class CreateCustomEventDto +{ + [Required] + [MaxLength(150)] + public required string Name { get; set; } + + [Required] + public bool IsPointsEvent { get; set; } + + [Required] + public bool IsParticipationEvent { get; set; } + + [Required] + [MaxLength(500)] + public required string Description { get; set; } + + [Required] + public Guid AllianceId { get; set; } + + [Required] + public required string EventDateString { get; set; } +} \ No newline at end of file diff --git a/Application/DataTransferObjects/CustomEvent/CustomEventDetailDto.cs b/Application/DataTransferObjects/CustomEvent/CustomEventDetailDto.cs new file mode 100644 index 0000000..2c56fe7 --- /dev/null +++ b/Application/DataTransferObjects/CustomEvent/CustomEventDetailDto.cs @@ -0,0 +1,8 @@ +using Application.DataTransferObjects.CustomEventParticipant; + +namespace Application.DataTransferObjects.CustomEvent; + +public class CustomEventDetailDto : CustomEventDto +{ + public ICollection CustomEventParticipants { get; set; } = []; +} \ No newline at end of file diff --git a/Application/DataTransferObjects/CustomEvent/CustomEventDto.cs b/Application/DataTransferObjects/CustomEvent/CustomEventDto.cs new file mode 100644 index 0000000..36912e8 --- /dev/null +++ b/Application/DataTransferObjects/CustomEvent/CustomEventDto.cs @@ -0,0 +1,25 @@ +namespace Application.DataTransferObjects.CustomEvent; + +public class CustomEventDto +{ + public Guid Id { get; set; } + + public Guid AllianceId { get; set; } + + public required string Name { get; set; } + + public required string Description { get; set; } + + public bool IsPointsEvent { get; set; } + + public bool IsParticipationEvent { get; set; } + + public DateTime EventDate { get; set; } + + public required string CreatedBy { get; set; } + + public DateTime? ModifiedOn { get; set; } + + public string? ModifiedBy { get; set; } + +} \ No newline at end of file diff --git a/Application/DataTransferObjects/CustomEvent/UpdateCustomEventDto.cs b/Application/DataTransferObjects/CustomEvent/UpdateCustomEventDto.cs new file mode 100644 index 0000000..52b404f --- /dev/null +++ b/Application/DataTransferObjects/CustomEvent/UpdateCustomEventDto.cs @@ -0,0 +1,27 @@ +using System.ComponentModel.DataAnnotations; + +namespace Application.DataTransferObjects.CustomEvent; + +public class UpdateCustomEventDto +{ + [Required] + public Guid Id { get; set; } + + [Required] + [MaxLength(150)] + public required string Name { get; set; } + + [Required] + public bool IsPointsEvent { get; set; } + + [Required] + public bool IsParticipationEvent { get; set; } + + [Required] + [MaxLength(500)] + public required string Description { get; set; } + + [Required] + public required string EventDateString { get; set; } + +} \ No newline at end of file diff --git a/Application/DataTransferObjects/CustomEventParticipant/CustomEventParticipantDto.cs b/Application/DataTransferObjects/CustomEventParticipant/CustomEventParticipantDto.cs new file mode 100644 index 0000000..ff9a004 --- /dev/null +++ b/Application/DataTransferObjects/CustomEventParticipant/CustomEventParticipantDto.cs @@ -0,0 +1,16 @@ +namespace Application.DataTransferObjects.CustomEventParticipant; + +public class CustomEventParticipantDto +{ + public Guid Id { get; set; } + + public Guid PlayerId { get; set; } + + public Guid CustomEventId { get; set; } + + public bool? Participated { get; set; } + + public long? AchievedPoints { get; set; } + + public required string PlayerName { get; set; } +} \ No newline at end of file diff --git a/Application/DataTransferObjects/DesertStorm/CreateDesertStormDto.cs b/Application/DataTransferObjects/DesertStorm/CreateDesertStormDto.cs index f54efd8..f8b4062 100644 --- a/Application/DataTransferObjects/DesertStorm/CreateDesertStormDto.cs +++ b/Application/DataTransferObjects/DesertStorm/CreateDesertStormDto.cs @@ -1,10 +1,25 @@ -namespace Application.DataTransferObjects.DesertStorm; +using System.ComponentModel.DataAnnotations; + +namespace Application.DataTransferObjects.DesertStorm; public class CreateDesertStormDto { - public Guid PlayerId { get; set; } + [Required] + public Guid AllianceId { get; set; } - public bool Registered { get; set; } + [Required] + public bool Won { get; set; } - public bool Participated { get; set; } + [Required] + public int OpposingParticipants { get; set; } + + [Required] + public int OpponentServer { get; set; } + + [Required] + public required string EventDate { get; set; } + + [Required] + [MaxLength(150)] + public required string OpponentName { get; set; } } \ No newline at end of file diff --git a/Application/DataTransferObjects/DesertStorm/DesertStormDetailDto.cs b/Application/DataTransferObjects/DesertStorm/DesertStormDetailDto.cs new file mode 100644 index 0000000..86560dd --- /dev/null +++ b/Application/DataTransferObjects/DesertStorm/DesertStormDetailDto.cs @@ -0,0 +1,8 @@ +using Application.DataTransferObjects.DesertStormParticipants; + +namespace Application.DataTransferObjects.DesertStorm; + +public class DesertStormDetailDto : DesertStormDto +{ + public ICollection DesertStormParticipants { get; set; } = []; +} \ No newline at end of file diff --git a/Application/DataTransferObjects/DesertStorm/DesertStormDto.cs b/Application/DataTransferObjects/DesertStorm/DesertStormDto.cs index dc23c9a..9ee55d6 100644 --- a/Application/DataTransferObjects/DesertStorm/DesertStormDto.cs +++ b/Application/DataTransferObjects/DesertStorm/DesertStormDto.cs @@ -4,11 +4,23 @@ public class DesertStormDto { public Guid Id { get; set; } - public bool Registered { get; set; } + public bool Won { get; set; } - public bool Participated { get; set; } + public int OpposingParticipants { get; set; } - public int Year { get; set; } + public int OpponentServer { get; set; } - public int CalendarWeek { get; set; } + public DateTime EventDate { get; set; } + + public required string OpponentName { get; set; } + + public Guid AllianceId { get; set; } + + public string? ModifiedBy { get; set; } + + public DateTime? ModifiedOn { get; set; } + + public required string CreatedBy { get; set; } + + public int Participants { get; set; } } \ No newline at end of file diff --git a/Application/DataTransferObjects/DesertStorm/UpdateDesertStormDto.cs b/Application/DataTransferObjects/DesertStorm/UpdateDesertStormDto.cs index 537f8b0..8d6dbb1 100644 --- a/Application/DataTransferObjects/DesertStorm/UpdateDesertStormDto.cs +++ b/Application/DataTransferObjects/DesertStorm/UpdateDesertStormDto.cs @@ -1,16 +1,23 @@ -namespace Application.DataTransferObjects.DesertStorm; +using System.ComponentModel.DataAnnotations; + +namespace Application.DataTransferObjects.DesertStorm; public class UpdateDesertStormDto { public Guid Id { get; set; } - public Guid PlayerId { get; set; } + [Required] + public bool Won { get; set; } - public bool Registered { get; set; } + [Required] + public int OpposingParticipants { get; set; } - public bool Participated { get; set; } + [Required] + public int OpponentServer { get; set; } - public int Year { get; set; } + public DateTime EventDate { get; set; } = DateTime.Now; - public int CalendarWeek { get; set; } + [Required] + [MaxLength(150)] + public required string OpponentName { get; set; } } \ No newline at end of file diff --git a/Application/DataTransferObjects/DesertStormParticipants/CreateDesertStormParticipantDto.cs b/Application/DataTransferObjects/DesertStormParticipants/CreateDesertStormParticipantDto.cs new file mode 100644 index 0000000..746ab73 --- /dev/null +++ b/Application/DataTransferObjects/DesertStormParticipants/CreateDesertStormParticipantDto.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.DataAnnotations; + +namespace Application.DataTransferObjects.DesertStormParticipants; + +public class CreateDesertStormParticipantDto +{ + [Required] + public Guid DesertStormId { get; set; } + + [Required] + public Guid PlayerId { get; set; } + + [Required] + public bool Registered { get; set; } + + [Required] + public bool Participated { get; set; } + + [Required] + public bool StartPlayer { get; set; } +} \ No newline at end of file diff --git a/Application/DataTransferObjects/DesertStormParticipants/DesertStormParticipantDto.cs b/Application/DataTransferObjects/DesertStormParticipants/DesertStormParticipantDto.cs new file mode 100644 index 0000000..49da6fe --- /dev/null +++ b/Application/DataTransferObjects/DesertStormParticipants/DesertStormParticipantDto.cs @@ -0,0 +1,18 @@ +namespace Application.DataTransferObjects.DesertStormParticipants; + +public class DesertStormParticipantDto +{ + public Guid Id { get; set; } + + public Guid DesertStormId { get; set; } + + public Guid PlayerId { get; set; } + + public required string PlayerName { get; set; } + + public bool Registered { get; set; } + + public bool Participated { get; set; } + + public bool StartPlayer { get; set; } +} \ No newline at end of file diff --git a/Application/DataTransferObjects/DesertStormParticipants/UpdateDesertStormParticipantDto.cs b/Application/DataTransferObjects/DesertStormParticipants/UpdateDesertStormParticipantDto.cs new file mode 100644 index 0000000..bea6721 --- /dev/null +++ b/Application/DataTransferObjects/DesertStormParticipants/UpdateDesertStormParticipantDto.cs @@ -0,0 +1,24 @@ +using System.ComponentModel.DataAnnotations; + +namespace Application.DataTransferObjects.DesertStormParticipants; + +public class UpdateDesertStormParticipantDto +{ + [Required] + public Guid Id { get; set; } + + [Required] + public Guid DesertStormId { get; set; } + + [Required] + public Guid PlayerId { get; set; } + + [Required] + public bool Registered { get; set; } + + [Required] + public bool Participated { get; set; } + + [Required] + public bool StartPlayer { get; set; } +} \ No newline at end of file diff --git a/Application/DataTransferObjects/MarshalGuard/CreateMarshalGuardDto.cs b/Application/DataTransferObjects/MarshalGuard/CreateMarshalGuardDto.cs index 801c67c..64f66b9 100644 --- a/Application/DataTransferObjects/MarshalGuard/CreateMarshalGuardDto.cs +++ b/Application/DataTransferObjects/MarshalGuard/CreateMarshalGuardDto.cs @@ -5,9 +5,18 @@ namespace Application.DataTransferObjects.MarshalGuard; public class CreateMarshalGuardDto { [Required] - public Guid PlayerId { get; set; } + public Guid AllianceId { get; set; } [Required] - public bool Participated { get; set; } + public int RewardPhase { get; set; } + + [Required] + public int Level { get; set; } + + [Required] + public int AllianceSize { get; set; } + + [Required] + public required string EventDate { get; set; } } \ No newline at end of file diff --git a/Application/DataTransferObjects/MarshalGuard/MarshalGuardDetailDto.cs b/Application/DataTransferObjects/MarshalGuard/MarshalGuardDetailDto.cs new file mode 100644 index 0000000..7ea7d47 --- /dev/null +++ b/Application/DataTransferObjects/MarshalGuard/MarshalGuardDetailDto.cs @@ -0,0 +1,8 @@ +using Application.DataTransferObjects.MarshalGuardParticipant; + +namespace Application.DataTransferObjects.MarshalGuard; + +public class MarshalGuardDetailDto : MarshalGuardDto +{ + public ICollection MarshalGuardParticipants { get; set; } = []; +} \ No newline at end of file diff --git a/Application/DataTransferObjects/MarshalGuard/MarshalGuardDto.cs b/Application/DataTransferObjects/MarshalGuard/MarshalGuardDto.cs index 38411a4..439c0a9 100644 --- a/Application/DataTransferObjects/MarshalGuard/MarshalGuardDto.cs +++ b/Application/DataTransferObjects/MarshalGuard/MarshalGuardDto.cs @@ -4,11 +4,21 @@ public class MarshalGuardDto { public Guid Id { get; set; } - public bool Participated { get; set; } + public Guid AllianceId { get; set; } - public int Year { get; set; } + public int Participants { get; set; } - public int Month { get; set; } + public int RewardPhase { get; set; } - public int Day { get; set; } + public int Level { get; set; } + + public int AllianceSize { get; set; } + + public DateTime EventDate { get; set; } + + public required string CreatedBy { get; set; } + + public DateTime? ModifiedOn { get; set; } + + public string? ModifiedBy { get; set; } } \ No newline at end of file diff --git a/Application/DataTransferObjects/MarshalGuard/UpdateMarshalGuardDto.cs b/Application/DataTransferObjects/MarshalGuard/UpdateMarshalGuardDto.cs index 57bbeec..8a4fc57 100644 --- a/Application/DataTransferObjects/MarshalGuard/UpdateMarshalGuardDto.cs +++ b/Application/DataTransferObjects/MarshalGuard/UpdateMarshalGuardDto.cs @@ -8,17 +8,17 @@ public class UpdateMarshalGuardDto public Guid Id { get; set; } [Required] - public Guid PlayerId { get; set; } + public Guid AllianceId { get; set; } [Required] - public bool Participated { get; set; } + public int Participants { get; set; } [Required] - public int Year { get; set; } + public int Level { get; set; } [Required] - public int Month { get; set; } + public int RewardPhase { get; set; } [Required] - public int Day { get; set; } + public required string EventDate { get; set; } } \ No newline at end of file diff --git a/Application/DataTransferObjects/MarshalGuardParticipant/CreateMarshalGuardParticipantDto.cs b/Application/DataTransferObjects/MarshalGuardParticipant/CreateMarshalGuardParticipantDto.cs new file mode 100644 index 0000000..5ff56bb --- /dev/null +++ b/Application/DataTransferObjects/MarshalGuardParticipant/CreateMarshalGuardParticipantDto.cs @@ -0,0 +1,10 @@ +namespace Application.DataTransferObjects.MarshalGuardParticipant; + +public class CreateMarshalGuardParticipantDto +{ + public Guid PlayerId { get; set; } + + public Guid MarshalGuardId { get; set; } + + public bool Participated { get; set; } +} \ No newline at end of file diff --git a/Application/DataTransferObjects/MarshalGuardParticipant/MarshalGuardParticipantDto.cs b/Application/DataTransferObjects/MarshalGuardParticipant/MarshalGuardParticipantDto.cs new file mode 100644 index 0000000..02183d4 --- /dev/null +++ b/Application/DataTransferObjects/MarshalGuardParticipant/MarshalGuardParticipantDto.cs @@ -0,0 +1,14 @@ +namespace Application.DataTransferObjects.MarshalGuardParticipant; + +public class MarshalGuardParticipantDto +{ + public Guid Id { get; set; } + + public Guid PlayerId { get; set; } + + public Guid MarshalGuardId { get; set; } + + public bool Participated { get; set; } + + public required string PlayerName { get; set; } +} \ No newline at end of file diff --git a/Application/DataTransferObjects/MarshalGuardParticipant/UpdateMarshalGuardParticipantDto.cs b/Application/DataTransferObjects/MarshalGuardParticipant/UpdateMarshalGuardParticipantDto.cs new file mode 100644 index 0000000..e4caf10 --- /dev/null +++ b/Application/DataTransferObjects/MarshalGuardParticipant/UpdateMarshalGuardParticipantDto.cs @@ -0,0 +1,18 @@ +using System.ComponentModel.DataAnnotations; + +namespace Application.DataTransferObjects.MarshalGuardParticipant; + +public class UpdateMarshalGuardParticipantDto +{ + [Required] + public Guid Id { get; set; } + + [Required] + public Guid PlayerId { get; set; } + + [Required] + public Guid MarshalGuardId { get; set; } + + [Required] + public bool Participated { get; set; } +} \ No newline at end of file diff --git a/Application/DataTransferObjects/Note/CreateNoteDto.cs b/Application/DataTransferObjects/Note/CreateNoteDto.cs index 6b0bcdf..db555c9 100644 --- a/Application/DataTransferObjects/Note/CreateNoteDto.cs +++ b/Application/DataTransferObjects/Note/CreateNoteDto.cs @@ -7,6 +7,8 @@ public class CreateNoteDto [Required] public Guid PlayerId { get; set; } + public DateTime CreatedOn { get; set; } = DateTime.Now; + [Required] [MaxLength(500)] public required string PlayerNote { get; set; } diff --git a/Application/DataTransferObjects/Note/NoteDto.cs b/Application/DataTransferObjects/Note/NoteDto.cs index 7381e6b..073a90d 100644 --- a/Application/DataTransferObjects/Note/NoteDto.cs +++ b/Application/DataTransferObjects/Note/NoteDto.cs @@ -4,5 +4,15 @@ public class NoteDto { public Guid Id { get; set; } + public Guid PlayerId { get; set; } + + public DateTime CreatedOn { get; set; } + + public required string CreatedBy { get; set; } + public required string PlayerNote { get; set; } + + public DateTime? ModifiedOn { get; set; } + + public string? ModifiedBy { get; set; } } \ No newline at end of file diff --git a/Application/DataTransferObjects/Player/CreatePlayerDto.cs b/Application/DataTransferObjects/Player/CreatePlayerDto.cs index 6f80e01..c491047 100644 --- a/Application/DataTransferObjects/Player/CreatePlayerDto.cs +++ b/Application/DataTransferObjects/Player/CreatePlayerDto.cs @@ -15,6 +15,5 @@ public class CreatePlayerDto public Guid AllianceId { get; set; } [Required] - [MaxLength(3)] - public required string Level { get; set; } + public int Level { get; set; } } \ No newline at end of file diff --git a/Application/DataTransferObjects/Player/PlayerDto.cs b/Application/DataTransferObjects/Player/PlayerDto.cs index e3a4d60..99980cb 100644 --- a/Application/DataTransferObjects/Player/PlayerDto.cs +++ b/Application/DataTransferObjects/Player/PlayerDto.cs @@ -1,12 +1,30 @@ -namespace Application.DataTransferObjects.Player; +using System.Runtime.InteropServices.JavaScript; + +namespace Application.DataTransferObjects.Player; public class PlayerDto { public Guid Id { get; set; } + public Guid RankId { get; set; } + + public Guid AllianceId { get; set; } + public required string PlayerName { get; set; } - public required string Level { get; set; } + public int Level { get; set; } public required string RankName { get; set; } + + public int NotesCount { get; set; } + + public int AdmonitionsCount { get; set; } + + public DateTime CreatedOn { get; set; } + + public required string CreatedBy { get; set; } + + public DateTime? ModifiedOn { get; set; } + + public string? ModifiedBy { get; set; } } \ No newline at end of file diff --git a/Application/DataTransferObjects/Player/UpdatePlayerDto.cs b/Application/DataTransferObjects/Player/UpdatePlayerDto.cs index b5c5f9c..0235f0e 100644 --- a/Application/DataTransferObjects/Player/UpdatePlayerDto.cs +++ b/Application/DataTransferObjects/Player/UpdatePlayerDto.cs @@ -13,6 +13,5 @@ public class UpdatePlayerDto public Guid RankId { get; set; } [Required] - [MaxLength(3)] - public required string Level { get; set; } + public int Level { get; set; } } \ No newline at end of file diff --git a/Application/DataTransferObjects/Rank/RankDto.cs b/Application/DataTransferObjects/Rank/RankDto.cs new file mode 100644 index 0000000..6104b37 --- /dev/null +++ b/Application/DataTransferObjects/Rank/RankDto.cs @@ -0,0 +1,8 @@ +namespace Application.DataTransferObjects.Rank; + +public class RankDto +{ + public Guid Id { get; set; } + + public required string Name { get; set; } +} \ No newline at end of file diff --git a/Application/DataTransferObjects/User/ChangePasswordDto.cs b/Application/DataTransferObjects/User/ChangePasswordDto.cs new file mode 100644 index 0000000..97e0aea --- /dev/null +++ b/Application/DataTransferObjects/User/ChangePasswordDto.cs @@ -0,0 +1,18 @@ +using System.ComponentModel.DataAnnotations; + +namespace Application.DataTransferObjects.User; + +public class ChangePasswordDto +{ + [Required] + public Guid UserId { get; set; } + + [Required] + public required string CurrentPassword { get; set; } + + [Required] + public required string NewPassword { get; set; } + + [Required] + public required string ConfirmPassword { get; set; } +} \ No newline at end of file diff --git a/Application/DataTransferObjects/User/UpdateUserDto.cs b/Application/DataTransferObjects/User/UpdateUserDto.cs new file mode 100644 index 0000000..78e14a7 --- /dev/null +++ b/Application/DataTransferObjects/User/UpdateUserDto.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations; + +namespace Application.DataTransferObjects.User; + +public class UpdateUserDto +{ + [Required] + public Guid Id { get; set; } + + [Required] + [MaxLength(200)] + public required string PlayerName { get; set; } + + [Required] + public required string Role { get; set; } +} \ No newline at end of file diff --git a/Application/DataTransferObjects/User/UserDto.cs b/Application/DataTransferObjects/User/UserDto.cs new file mode 100644 index 0000000..f8f9c22 --- /dev/null +++ b/Application/DataTransferObjects/User/UserDto.cs @@ -0,0 +1,14 @@ +namespace Application.DataTransferObjects.User; + +public class UserDto +{ + public Guid Id { get; set; } + + public Guid AllianceId { get; set; } + + public required string PlayerName { get; set; } + + public required string Email { get; set; } + + public required string Role { get; set; } +} \ No newline at end of file diff --git a/Application/DataTransferObjects/VsDuel/CreateVsDuelDto.cs b/Application/DataTransferObjects/VsDuel/CreateVsDuelDto.cs index a0bfce9..cc2dae0 100644 --- a/Application/DataTransferObjects/VsDuel/CreateVsDuelDto.cs +++ b/Application/DataTransferObjects/VsDuel/CreateVsDuelDto.cs @@ -5,9 +5,25 @@ namespace Application.DataTransferObjects.VsDuel; public class CreateVsDuelDto { [Required] - public Guid PlayerId { get; set; } - + public Guid AllianceId { get; set; } + [Required] - public int WeeklyPoints { get; set; } + public required string EventDate { get; set; } + + [Required] + public bool Won { get; set; } + + [Required] + [MaxLength(150)] + public required string OpponentName { get; set; } + + [Required] + public int OpponentServer { get; set; } + + [Required] + public long OpponentPower { get; set; } + + [Required] + public int OpponentSize { get; set; } } \ No newline at end of file diff --git a/Application/DataTransferObjects/VsDuel/UpdateVsDuelDto.cs b/Application/DataTransferObjects/VsDuel/UpdateVsDuelDto.cs index 27081c8..95d14d1 100644 --- a/Application/DataTransferObjects/VsDuel/UpdateVsDuelDto.cs +++ b/Application/DataTransferObjects/VsDuel/UpdateVsDuelDto.cs @@ -8,14 +8,24 @@ public class UpdateVsDuelDto public Guid Id { get; set; } [Required] - public Guid PlayerId { get; set; } + public Guid AllianceId { get; set; } [Required] - public int WeeklyPoints { get; set; } + public required string EventDate { get; set; } [Required] - public int Year { get; set; } + public bool Won { get; set; } [Required] - public int CalendarWeek { get; set; } + [MaxLength(150)] + public required string OpponentName { get; set; } + + [Required] + public int OpponentServer { get; set; } + + [Required] + public long OpponentPower { get; set; } + + [Required] + public int OpponentSize { get; set; } } \ No newline at end of file diff --git a/Application/DataTransferObjects/VsDuel/VsDuelDetailDto.cs b/Application/DataTransferObjects/VsDuel/VsDuelDetailDto.cs new file mode 100644 index 0000000..ea0d70e --- /dev/null +++ b/Application/DataTransferObjects/VsDuel/VsDuelDetailDto.cs @@ -0,0 +1,8 @@ +using Application.DataTransferObjects.VsDuelParticipant; + +namespace Application.DataTransferObjects.VsDuel; + +public class VsDuelDetailDto : VsDuelDto +{ + public ICollection VsDuelParticipants { get; set; } = []; +} \ No newline at end of file diff --git a/Application/DataTransferObjects/VsDuel/VsDuelDto.cs b/Application/DataTransferObjects/VsDuel/VsDuelDto.cs index 817549d..88890bf 100644 --- a/Application/DataTransferObjects/VsDuel/VsDuelDto.cs +++ b/Application/DataTransferObjects/VsDuel/VsDuelDto.cs @@ -1,13 +1,29 @@ -namespace Application.DataTransferObjects.VsDuel; +using Database.Entities; + +namespace Application.DataTransferObjects.VsDuel; public class VsDuelDto { public Guid Id { get; set; } - public int WeeklyPoints { get; set; } + public Guid AllianceId { get; set; } - public int Year { get; set; } + public DateTime EventDate { get; set; } - public int CalendarWeek { get; set; } + public required string CreatedBy { get; set; } + + public bool Won { get; set; } + + public required string OpponentName { get; set; } + + public int OpponentServer { get; set; } + + public long OpponentPower { get; set; } + + public int OpponentSize { get; set; } + + public DateTime? ModifiedOn { get; set; } + + public string? ModifiedBy { get; set; } } \ No newline at end of file diff --git a/Application/DataTransferObjects/VsDuelParticipant/VsDuelParticipantDto.cs b/Application/DataTransferObjects/VsDuelParticipant/VsDuelParticipantDto.cs new file mode 100644 index 0000000..290c584 --- /dev/null +++ b/Application/DataTransferObjects/VsDuelParticipant/VsDuelParticipantDto.cs @@ -0,0 +1,14 @@ +namespace Application.DataTransferObjects.VsDuelParticipant; + +public class VsDuelParticipantDto +{ + public Guid Id { get; set; } + + public Guid PlayerId { get; set; } + + public Guid VsDuelId { get; set; } + + public long WeeklyPoints { get; set; } + + public required string PlayerName { get; set; } +} \ No newline at end of file diff --git a/Application/Errors/AuthenticationErrors.cs b/Application/Errors/AuthenticationErrors.cs index 4d2445c..8de188f 100644 --- a/Application/Errors/AuthenticationErrors.cs +++ b/Application/Errors/AuthenticationErrors.cs @@ -4,9 +4,18 @@ public static class AuthenticationErrors { public static readonly Error LoginFailed = new("Error.Authentication.LoginFailed", "Email or password incorrect"); + public static readonly Error EmailNotConfirmed = + new("Error.Authentication.EmailNotConfirmed", "Email is not confirmed"); + public static readonly Error RegisterFailed = new("Error.Authentication.RegisterFailed", "Could not create an account"); public static readonly Error AllianceAlreadyExists = new("Error.Authentication.AllianceAlreadyExists", "Alliance already exists"); + + public static readonly Error ResendConfirmationEmailFailed = new("Error.Authentication.ResendConfirmEmailFailed", + "Error while resend the email confirmation email"); + + public static readonly Error InviteUserFailed = new("Error.Authentication.InviteUser", + "Error while send email for inviting"); } \ No newline at end of file diff --git a/Application/Errors/CustomEventErrors.cs b/Application/Errors/CustomEventErrors.cs new file mode 100644 index 0000000..ba5a113 --- /dev/null +++ b/Application/Errors/CustomEventErrors.cs @@ -0,0 +1,9 @@ +namespace Application.Errors; + +public static class CustomEventErrors +{ + public static readonly Error NotFound = new("Error.CustomEvent.NotFound", + "The custom event with the specified identifier was not found"); + + public static readonly Error IdConflict = new("Error.CustomEvent.IdConflict", "There is a conflict with the id's"); +} \ No newline at end of file diff --git a/Application/Errors/RoleErrors.cs b/Application/Errors/RoleErrors.cs new file mode 100644 index 0000000..142f819 --- /dev/null +++ b/Application/Errors/RoleErrors.cs @@ -0,0 +1,7 @@ +namespace Application.Errors; + +public static class RoleErrors +{ + public static readonly Error NotFound = new("Error.Role.NotFound", + "The role with the specified identifier was not found"); +} \ No newline at end of file diff --git a/Application/Errors/UserErrors.cs b/Application/Errors/UserErrors.cs new file mode 100644 index 0000000..912ada9 --- /dev/null +++ b/Application/Errors/UserErrors.cs @@ -0,0 +1,15 @@ +namespace Application.Errors; + +public class UserErrors +{ + public static readonly Error NotFound = new("Error.User.NotFound", + "The user with the specified identifier was not found"); + + public static readonly Error IdConflict = new("Error.User.IdConflict", "There is a conflict with the id's"); + + public static readonly Error CurrentPasswordNotMatch = new("Error.User.CurrentPassword", "The current password is invalid"); + + public static readonly Error ChangePasswordFailed = new("Error.User.ChangePasswordFailed", "Password change failed"); + + public static readonly Error ConfirmPasswordNotMatch = new("Error.User.ConfirmPasswordNotMatch", "The confirm password not match"); +} \ No newline at end of file diff --git a/Application/Errors/VsDuelParticipantErrors.cs b/Application/Errors/VsDuelParticipantErrors.cs new file mode 100644 index 0000000..f47bbaa --- /dev/null +++ b/Application/Errors/VsDuelParticipantErrors.cs @@ -0,0 +1,9 @@ +namespace Application.Errors; + +public class VsDuelParticipantErrors +{ + public static readonly Error NotFound = new("Error.VsDuelParticipant.NotFound", + "The VsDuelParticipant with the specified identifier was not found"); + + public static readonly Error IdConflict = new("Error.VsDuelParticipant.IdConflict", "There is a conflict with the id's"); +} \ No newline at end of file diff --git a/Application/Helpers/Email/EmailTemplateFactory.cs b/Application/Helpers/Email/EmailTemplateFactory.cs new file mode 100644 index 0000000..73ff80b --- /dev/null +++ b/Application/Helpers/Email/EmailTemplateFactory.cs @@ -0,0 +1,18 @@ +using Application.Interfaces; + +namespace Application.Helpers.Email; + +public static class EmailTemplateFactory +{ + public static IEmailTemplate GetEmailTemplate(string languageCode) + { + return languageCode switch + { + "en" => new EnglishEmailTemplate(), + "de" => new GermanEmailTemplate(), + "fr" => new FrenchEmailTemplate(), + "it" => new ItalianEmailTemplate(), + _ => new EnglishEmailTemplate() + }; + } +} \ No newline at end of file diff --git a/Application/Helpers/Email/EnglishEmailTemplate.cs b/Application/Helpers/Email/EnglishEmailTemplate.cs new file mode 100644 index 0000000..1d44798 --- /dev/null +++ b/Application/Helpers/Email/EnglishEmailTemplate.cs @@ -0,0 +1,301 @@ +using Application.Classes; +using Application.Interfaces; + +namespace Application.Helpers.Email; + +public class EnglishEmailTemplate : IEmailTemplate +{ + public EmailContent ConfirmEmail(string userName, string callBack) + { + var emailContent = new EmailContent() + { + Subject = "Please Confirm Your Email Address", + Content = $@" + + + + + +
+
+

Welcome, {userName}!

+
+
+

Thank you for registering with Last War Playermanager.

+

Please confirm your email address by clicking the button below. This confirmation is only valid for 2 hours:

+ Confirm Email +
+ +
+ + " + }; + + return emailContent; + } + + public EmailContent ResendConfirmationEmail(string userName, string callBack) + { + var emailContent = new EmailContent() + { + Subject = "Resend Confirmation of Your Email Address", + Content = $@" + + + + + +
+
+

Hello, {userName}!

+
+
+

You have requested to resend the confirmation of your email address.

+

Please confirm your email address by clicking the button below. This confirmation is valid for 2 hours only:

+ Confirm Email +
+ +
+ + " + }; + + return emailContent; + } + + public EmailContent InviteUserEmail(string invitingUserName, string allianceName, string callBack) + { + var emailContent = new EmailContent() + { + Subject = "Invitation to Last War Playermanager", + Content = $@" + + + + + +
+
+

Invitation to Last War Playermanager

+
+
+

{invitingUserName} is inviting you to join the Last War Playermanager.

+

Your alliance, {allianceName}, is looking forward to your participation! Click the button below to accept the invitation:

+ Join Now +
+ +
+ + " + }; + + return emailContent; + } + + public EmailContent ResetPasswordEmail(string userName, string callBack) + { + var emailContent = new EmailContent() + { + Subject = "Password Reset - Last War Playermanager", + Content = $@" + + + + + +
+
+

Password Reset Request

+
+
+

Hello {userName},

+

We received a request to reset the password for your Last War Playermanager account. If you did not make this request, you can simply ignore this email.

+

To reset your password, please click the button below. The link is valid for the next 2 hours:

+ Reset Password +
+ +
+ + " + }; + + return emailContent; + + } +} \ No newline at end of file diff --git a/Application/Helpers/Email/FrenchEmailTemplate.cs b/Application/Helpers/Email/FrenchEmailTemplate.cs new file mode 100644 index 0000000..efb832d --- /dev/null +++ b/Application/Helpers/Email/FrenchEmailTemplate.cs @@ -0,0 +1,165 @@ +using Application.Classes; +using Application.Interfaces; + +namespace Application.Helpers.Email; + +public class FrenchEmailTemplate : IEmailTemplate +{ + public EmailContent ConfirmEmail(string userName, string callBack) + { + throw new NotImplementedException(); + } + + public EmailContent ResendConfirmationEmail(string userName, string callBack) + { + throw new NotImplementedException(); + } + + public EmailContent InviteUserEmail(string invitingUserName, string allianceName, string callBack) + { + var emailContent = new EmailContent() + { + Subject = "Invitation au Last War Playermanager", + Content = $@" + + + + + +
+
+

Invitation au Last War Playermanager

+
+
+

{invitingUserName} vous invite à rejoindre le Last War Playermanager.

+

Votre alliance, {allianceName}, attend avec impatience votre participation ! Cliquez sur le bouton ci-dessous pour accepter l'invitation :

+ Rejoindre maintenant +
+ +
+ + " + }; + + return emailContent; + } + + public EmailContent ResetPasswordEmail(string userName, string callBack) + { + var emailContent = new EmailContent() + { + Subject = "Réinitialisation du mot de passe - Last War Playermanager", + Content = $@" + + + + + +
+
+

Demande de réinitialisation du mot de passe

+
+
+

Bonjour {userName},

+

Nous avons reçu une demande de réinitialisation du mot de passe pour votre compte Last War Playermanager. Si vous n'avez pas fait cette demande, vous pouvez ignorer cet e-mail.

+

Pour réinitialiser votre mot de passe, veuillez cliquer sur le bouton ci-dessous. Le lien est valable pour les 2 prochaines heures :

+ Réinitialiser le mot de passe +
+ +
+ + " + }; + + return emailContent; + + } +} \ No newline at end of file diff --git a/Application/Helpers/Email/GermanEmailTemplate.cs b/Application/Helpers/Email/GermanEmailTemplate.cs new file mode 100644 index 0000000..1da04f6 --- /dev/null +++ b/Application/Helpers/Email/GermanEmailTemplate.cs @@ -0,0 +1,300 @@ +using Application.Classes; +using Application.Interfaces; + +namespace Application.Helpers.Email; + +public class GermanEmailTemplate : IEmailTemplate +{ + public EmailContent ConfirmEmail(string userName, string callBack) + { + var emailContent = new EmailContent() + { + Subject = "Bitte bestätigen Sie Ihre E-Mail-Adresse", + Content = $@" + + + + + +
+
+

Willkommen, {userName}!

+
+
+

Vielen Dank, dass Sie sich für Last War Playermanager registriert haben.

+

Bitte bestätigen Sie Ihre E-Mail-Adresse, indem Sie auf den folgenden Button klicken. Diese Bestätigung ist nur 2 Stunden gültig:

+ E-Mail bestätigen +
+ +
+ + " + }; + + return emailContent; + } + + public EmailContent ResendConfirmationEmail(string userName, string callBack) + { + var emailContent = new EmailContent() + { + Subject = "Erneute Bestätigung Ihrer E-Mail-Adresse", + Content = $@" + + + + + +
+
+

Hallo, {userName}!

+
+
+

Sie haben eine erneute Bestätigung Ihrer E-Mail-Adresse angefordert.

+

Bitte bestätigen Sie Ihre E-Mail-Adresse, indem Sie auf den folgenden Button klicken. Diese Bestätigung ist nur 2 Stunden gültig:

+ E-Mail bestätigen +
+ +
+ + " + }; + + return emailContent; + } + + public EmailContent InviteUserEmail(string invitingUserName, string allianceName, string callBack) + { + var emailContent = new EmailContent() + { + Subject = "Einladung zum Last War Playermanager", + Content = $@" + + + + + +
+
+

Einladung zum Last War Playermanager

+
+
+

{invitingUserName} lädt Sie ein, dem Last War Playermanager beizutreten.

+

Ihre Allianz, {allianceName}, freut sich auf Ihre Teilnahme! Klicken Sie auf den folgenden Button, um der Einladung zu folgen:

+ Jetzt beitreten +
+ +
+ + " + }; + + return emailContent; + } + + public EmailContent ResetPasswordEmail(string userName, string callBack) + { + var emailContent = new EmailContent() + { + Subject = "Passwort-Zurücksetzung - Last War Playermanager", + Content = $@" + + + + + +
+
+

Anfrage zur Passwort-Zurücksetzung

+
+
+

Hallo {userName},

+

Wir haben eine Anfrage erhalten, das Passwort für dein Last War Playermanager-Konto zurückzusetzen. Wenn du diese Anfrage nicht gestellt hast, kannst du diese E-Mail einfach ignorieren.

+

Um dein Passwort zurückzusetzen, klicke bitte auf den untenstehenden Button. Der Link ist für die nächsten 2 Stunden gültig:

+ Passwort zurücksetzen +
+ +
+ + " + }; + + return emailContent; + } +} diff --git a/Application/Helpers/Email/ItalianEmailTemplate.cs b/Application/Helpers/Email/ItalianEmailTemplate.cs new file mode 100644 index 0000000..6b39048 --- /dev/null +++ b/Application/Helpers/Email/ItalianEmailTemplate.cs @@ -0,0 +1,165 @@ +using Application.Classes; +using Application.Interfaces; + +namespace Application.Helpers.Email; + +public class ItalianEmailTemplate : IEmailTemplate +{ + public EmailContent ConfirmEmail(string userName, string callBack) + { + throw new NotImplementedException(); + } + + public EmailContent ResendConfirmationEmail(string userName, string callBack) + { + throw new NotImplementedException(); + } + + public EmailContent InviteUserEmail(string invitingUserName, string allianceName, string callBack) + { + var emailContent = new EmailContent() + { + Subject = "Invito al Last War Playermanager", + Content = $@" + + + + + +
+
+

Invito al Last War Playermanager

+
+
+

{invitingUserName} ti invita a unirti al Last War Playermanager.

+

La tua alleanza, {allianceName}, è in attesa della tua partecipazione! Clicca sul pulsante qui sotto per accettare l'invito:

+ Unisciti ora +
+ +
+ + " + }; + + return emailContent; + } + + public EmailContent ResetPasswordEmail(string userName, string callBack) + { + var emailContent = new EmailContent() + { + Subject = "Reimpostazione della password - Last War Playermanager", + Content = $@" + + + + + +
+
+

Richiesta di reimpostazione della password

+
+
+

Ciao {userName},

+

Abbiamo ricevuto una richiesta di reimpostazione della password per il tuo account Last War Playermanager. Se non hai fatto questa richiesta, puoi ignorare questa email.

+

Per reimpostare la tua password, fai clic sul pulsante qui sotto. Il link è valido per le prossime 2 ore:

+ Reimposta la password +
+ +
+ + " + }; + + return emailContent; + + } +} \ No newline at end of file diff --git a/Application/Helpers/HttpExtensions.cs b/Application/Helpers/HttpExtensions.cs new file mode 100644 index 0000000..895943f --- /dev/null +++ b/Application/Helpers/HttpExtensions.cs @@ -0,0 +1,22 @@ +using System.Web; + +namespace Application.Helpers; + +public static class HttpExtensions +{ + public static Uri AddQueryParam(this Uri uri, string name, string value) + { + var httpValueCollection = HttpUtility.ParseQueryString(uri.Query); + + httpValueCollection.Remove(name); + + httpValueCollection.Add(name, value); + + var uriBuilder = new UriBuilder(uri) + { + Query = httpValueCollection.ToString() ?? string.Empty, + }; + + return uriBuilder.Uri; + } +} \ No newline at end of file diff --git a/Application/Interfaces/IAdmonitionRepository.cs b/Application/Interfaces/IAdmonitionRepository.cs index 2d891a1..49c07cd 100644 --- a/Application/Interfaces/IAdmonitionRepository.cs +++ b/Application/Interfaces/IAdmonitionRepository.cs @@ -11,9 +11,9 @@ public interface IAdmonitionRepository Task> GetAdmonitionAsync(Guid admonitionId, CancellationToken cancellationToken); - Task> CreateAdmonitionAsync(CreateAdmonitionDto createAdmonitionDto, CancellationToken cancellationToken); + Task> CreateAdmonitionAsync(CreateAdmonitionDto createAdmonitionDto, string createdBy, CancellationToken cancellationToken); - Task> UpdateAdmonitionAsync(UpdateAdmonitionDto updateAdmonitionDto, CancellationToken cancellationToken); + Task> UpdateAdmonitionAsync(UpdateAdmonitionDto updateAdmonitionDto, string modifiedBy, CancellationToken cancellationToken); Task> DeleteAdmonitionAsync(Guid admonitionId, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/Application/Interfaces/IAllianceRepository.cs b/Application/Interfaces/IAllianceRepository.cs index 4547afc..a4398d7 100644 --- a/Application/Interfaces/IAllianceRepository.cs +++ b/Application/Interfaces/IAllianceRepository.cs @@ -9,9 +9,7 @@ public interface IAllianceRepository Task> GetAllianceAsync(Guid allianceId, CancellationToken cancellationToken); - Task> CreateAllianceAsync(CreateAllianceDto createAllianceDto, CancellationToken cancellationToken); - - Task> UpdateAllianceAsync(UpdateAllianceDto updateAllianceDto, CancellationToken cancellationToken); + Task> UpdateAllianceAsync(UpdateAllianceDto updateAllianceDto, string modifiedBy, CancellationToken cancellationToken); Task> DeleteAllianceAsync(Guid allianceId, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/Application/Interfaces/IAuthenticationRepository.cs b/Application/Interfaces/IAuthenticationRepository.cs index 05bba9c..b1f5cb0 100644 --- a/Application/Interfaces/IAuthenticationRepository.cs +++ b/Application/Interfaces/IAuthenticationRepository.cs @@ -7,5 +7,18 @@ public interface IAuthenticationRepository { Task> LoginAsync(LoginRequestDto loginRequestDto, CancellationToken cancellationToken); - Task> RegisterToApplicationAsync(RegisterRequestDto registerRequestDto, CancellationToken cancellationToken); + Task RegisterToApplicationAsync(SignUpRequestDto signUpRequestDto, CancellationToken cancellationToken); + + Task RegisterUserAsync(RegisterUserDto registerUserDto, CancellationToken cancellationToken); + + Task EmailConfirmationAsync(ConfirmEmailRequestDto confirmEmailRequestDto); + + Task ResendConfirmationEmailAsync(EmailConfirmationRequestDto emailConfirmationRequestDto); + + Task InviteUserAsync(InviteUserDto inviteUserDto, CancellationToken cancellationToken); + + Task ResetPasswordAsync(ResetPasswordDto resetPasswordDto); + + Task ForgotPasswordAsync(ForgotPasswordDto forgotPasswordDto); + } \ No newline at end of file diff --git a/Application/Interfaces/IClaimTypeService.cs b/Application/Interfaces/IClaimTypeService.cs new file mode 100644 index 0000000..ddb84e1 --- /dev/null +++ b/Application/Interfaces/IClaimTypeService.cs @@ -0,0 +1,8 @@ +using System.Security.Claims; + +namespace Application.Interfaces; + +public interface IClaimTypeService +{ + string GetFullName(ClaimsPrincipal claimsPrincipal); +} \ No newline at end of file diff --git a/Application/Interfaces/ICustomEventRepository.cs b/Application/Interfaces/ICustomEventRepository.cs new file mode 100644 index 0000000..c2c25c9 --- /dev/null +++ b/Application/Interfaces/ICustomEventRepository.cs @@ -0,0 +1,21 @@ +using Application.Classes; +using Application.DataTransferObjects.CustomEvent; + +namespace Application.Interfaces; + +public interface ICustomEventRepository +{ + Task> GetCustomEventAsync(Guid customEventId, CancellationToken cancellationToken); + + Task> GetCustomEventDetailAsync(Guid customEventId, CancellationToken cancellationToken); + + Task>> GetAllianceCustomEventsAsync(Guid allianceId, int take, CancellationToken cancellationToken); + + Task> CreateCustomEventAsync(CreateCustomEventDto createCustomEventDto, string createdBy, + CancellationToken cancellationToken); + + Task> UpdateCustomEventAsync(UpdateCustomEventDto updateCustomEventDto, string modifiedBy, + CancellationToken cancellationToken); + + Task> DeleteCustomEventAsync(Guid customEventId, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/Application/Interfaces/IDesertStormParticipantRepository.cs b/Application/Interfaces/IDesertStormParticipantRepository.cs new file mode 100644 index 0000000..c849568 --- /dev/null +++ b/Application/Interfaces/IDesertStormParticipantRepository.cs @@ -0,0 +1,17 @@ +using Application.Classes; +using Application.DataTransferObjects.DesertStormParticipants; + +namespace Application.Interfaces; + +public interface IDesertStormParticipantRepository +{ + Task> GetDesertStormParticipantAsync(Guid desertStormParticipantId, + CancellationToken cancellationToken); + Task> InsertDesertStormParticipantAsync( + List createDesertStormParticipants, CancellationToken cancellationToken); + + Task>> GetPlayerDesertStormParticipantsAsync(Guid playerId, int last, CancellationToken cancellationToken); + + Task> UpdateDesertStormParticipantAsync( + UpdateDesertStormParticipantDto updateDesertStormParticipantDto, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/Application/Interfaces/IDesertStormRepository.cs b/Application/Interfaces/IDesertStormRepository.cs index 0508f63..c37611e 100644 --- a/Application/Interfaces/IDesertStormRepository.cs +++ b/Application/Interfaces/IDesertStormRepository.cs @@ -7,11 +7,13 @@ public interface IDesertStormRepository { Task> GetDesertStormAsync(Guid desertStormId, CancellationToken cancellationToken); - Task>> GetPlayerDesertStormsAsync(Guid playerId, CancellationToken cancellationToken); + Task>> GetAllianceDesertStormsAsync(Guid allianceId, int take, CancellationToken cancellationToken); - Task> CreateDesertStormAsync(CreateDesertStormDto createDesertStormDto, CancellationToken cancellationToken); + Task> GetDesertStormDetailAsync(Guid desertStormId, CancellationToken cancellationToken); - Task> UpdateDesertStormAsync(UpdateDesertStormDto updateDesertStormDto, CancellationToken cancellationToken); + Task> CreateDesertStormAsync(CreateDesertStormDto createDesertStormDto, string createdBy, CancellationToken cancellationToken); + + Task> UpdateDesertStormAsync(UpdateDesertStormDto updateDesertStormDto, string modifiedBy, CancellationToken cancellationToken); Task> DeleteDesertStormAsync(Guid desertStormId, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/Application/Interfaces/IEmailTemplate.cs b/Application/Interfaces/IEmailTemplate.cs new file mode 100644 index 0000000..fa1d5c9 --- /dev/null +++ b/Application/Interfaces/IEmailTemplate.cs @@ -0,0 +1,14 @@ +using Application.Classes; + +namespace Application.Interfaces; + +public interface IEmailTemplate +{ + public EmailContent ConfirmEmail(string userName, string callBack); + + public EmailContent ResendConfirmationEmail (string userName, string callBack); + + public EmailContent InviteUserEmail(string invitingUserName, string allianceName, string callBack); + + public EmailContent ResetPasswordEmail(string userName, string callBack); +} \ No newline at end of file diff --git a/Application/Interfaces/IMarshalGuardParticipantRepository.cs b/Application/Interfaces/IMarshalGuardParticipantRepository.cs new file mode 100644 index 0000000..a8b480a --- /dev/null +++ b/Application/Interfaces/IMarshalGuardParticipantRepository.cs @@ -0,0 +1,17 @@ +using Application.Classes; +using Application.DataTransferObjects.MarshalGuardParticipant; + +namespace Application.Interfaces; + +public interface IMarshalGuardParticipantRepository +{ + Task> GetMarshalGuardParticipantAsync(Guid marshalGuardParticipantId, + CancellationToken cancellationToken); + Task> InsertMarshalGuardParticipantAsync( + List createMarshalGuardParticipantsDto, CancellationToken cancellationToken); + + Task>> GetPlayerMarshalParticipantsAsync(Guid playerId, int last, CancellationToken cancellationToken); + + Task> UpdateMarshalGuardParticipantAsync( + UpdateMarshalGuardParticipantDto updateMarshalGuardParticipantDto, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/Application/Interfaces/IMarshalGuardRepository.cs b/Application/Interfaces/IMarshalGuardRepository.cs index 9a2e8a4..24c7f87 100644 --- a/Application/Interfaces/IMarshalGuardRepository.cs +++ b/Application/Interfaces/IMarshalGuardRepository.cs @@ -7,11 +7,13 @@ public interface IMarshalGuardRepository { Task> GetMarshalGuardAsync(Guid marshalGuardId, CancellationToken cancellationToken); - Task>> GetPlayerMarshalGuardsAsync(Guid playerId, CancellationToken cancellationToken); + Task> GetMarshalGuardDetailAsync(Guid marshalGuardId, CancellationToken cancellationToken); - Task> CreateMarshalGuardAsync(CreateMarshalGuardDto createMarshalGuardDto, CancellationToken cancellationToken); + Task>> GetAllianceMarshalGuardsAsync(Guid allianceId, int take, CancellationToken cancellationToken); - Task> UpdateMarshalGuardAsync(UpdateMarshalGuardDto updateMarshalGuardDto, CancellationToken cancellationToken); + Task> CreateMarshalGuardsAsync(CreateMarshalGuardDto createMarshalGuardDto, string createdBy, CancellationToken cancellationToken); + + Task> UpdateMarshalGuardAsync(UpdateMarshalGuardDto updateMarshalGuardDto, string modifiedBy, CancellationToken cancellationToken); Task> DeleteMarshalGuardAsync(Guid marshalGuardId, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/Application/Interfaces/INoteRepository.cs b/Application/Interfaces/INoteRepository.cs index 62129ea..6f0a39e 100644 --- a/Application/Interfaces/INoteRepository.cs +++ b/Application/Interfaces/INoteRepository.cs @@ -9,9 +9,9 @@ public interface INoteRepository Task>> GetPlayerNotesAsync(Guid playerId, CancellationToken cancellationToken); - Task> CreateNoteAsync(CreateNoteDto createNoteDto, CancellationToken cancellationToken); + Task> CreateNoteAsync(CreateNoteDto createNoteDto, string createdBy, CancellationToken cancellationToken); - Task> UpdateNoteAsync(UpdateNoteDto updateNoteDto, CancellationToken cancellationToken); + Task> UpdateNoteAsync(UpdateNoteDto updateNoteDto, string modifiedBy, CancellationToken cancellationToken); Task> DeleteNoteAsync(Guid noteId, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/Application/Interfaces/IPlayerRepository.cs b/Application/Interfaces/IPlayerRepository.cs index c3a44e6..6dda445 100644 --- a/Application/Interfaces/IPlayerRepository.cs +++ b/Application/Interfaces/IPlayerRepository.cs @@ -9,9 +9,9 @@ public interface IPlayerRepository Task>> GetAlliancePlayersAsync(Guid allianceId, CancellationToken cancellationToken); - Task> CreatePlayerAsync(CreatePlayerDto createPlayerDto, CancellationToken cancellationToken); + Task> CreatePlayerAsync(CreatePlayerDto createPlayerDto, string createdBy, CancellationToken cancellationToken); - Task> UpdatePlayerAsync(UpdatePlayerDto updatePlayerDto, CancellationToken cancellationToken); + Task> UpdatePlayerAsync(UpdatePlayerDto updatePlayerDto, string modifiedBy, CancellationToken cancellationToken); Task> DeletePlayerAsync(Guid playerIId, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/Application/Interfaces/IRankRepository.cs b/Application/Interfaces/IRankRepository.cs new file mode 100644 index 0000000..cd1f76c --- /dev/null +++ b/Application/Interfaces/IRankRepository.cs @@ -0,0 +1,9 @@ +using Application.Classes; +using Application.DataTransferObjects.Rank; + +namespace Application.Interfaces; + +public interface IRankRepository +{ + Task>> GetRanksAsync(CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/Application/Interfaces/IUserRepository.cs b/Application/Interfaces/IUserRepository.cs new file mode 100644 index 0000000..85302a3 --- /dev/null +++ b/Application/Interfaces/IUserRepository.cs @@ -0,0 +1,17 @@ +using Application.Classes; +using Application.DataTransferObjects.User; + +namespace Application.Interfaces; + +public interface IUserRepository +{ + Task>> GetAllianceUsersAsync(Guid allianceId, CancellationToken cancellationToken); + + Task> GetUserAsync(Guid userId, CancellationToken cancellationToken); + + Task ChangeUserPasswordAsync(ChangePasswordDto changePasswordDto, CancellationToken cancellationToken); + + Task> UpdateUserAsync(UpdateUserDto updateUserDto, CancellationToken cancellationToken); + + Task DeleteUserAsync(Guid userId, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/Application/Interfaces/IVsDuelParticipantRepository.cs b/Application/Interfaces/IVsDuelParticipantRepository.cs new file mode 100644 index 0000000..b4ac498 --- /dev/null +++ b/Application/Interfaces/IVsDuelParticipantRepository.cs @@ -0,0 +1,9 @@ +using Application.Classes; +using Application.DataTransferObjects.VsDuelParticipant; + +namespace Application.Interfaces; + +public interface IVsDuelParticipantRepository +{ + Task> UpdateVsDuelParticipant(VsDuelParticipantDto vsDuelParticipantDto, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/Application/Interfaces/IVsDuelRepository.cs b/Application/Interfaces/IVsDuelRepository.cs index a3b20aa..3ca0d90 100644 --- a/Application/Interfaces/IVsDuelRepository.cs +++ b/Application/Interfaces/IVsDuelRepository.cs @@ -7,11 +7,13 @@ public interface IVsDuelRepository { Task> GetVsDuelAsync(Guid vsDuelId, CancellationToken cancellationToken); - Task>> GetPlayerVsDuelsAsync(Guid playerId, CancellationToken cancellationToken); + Task> GetVsDuelDetailAsync(Guid vsDuelId, CancellationToken cancellationToken); - Task> CreateVsDuelAsync(CreateVsDuelDto createVsDuelDto, CancellationToken cancellationToken); + Task>> GetAllianceVsDuelsAsync(Guid allianceId, int take, CancellationToken cancellationToken); - Task> UpdateVsDuelAsync(UpdateVsDuelDto updateVsDuelDto, CancellationToken cancellationToken); + Task> CreateVsDuelAsync(CreateVsDuelDto createVsDuelDto, string createdBy, CancellationToken cancellationToken); + + Task> UpdateVsDuelAsync(UpdateVsDuelDto updateVsDuelDto, string modifiedBy, CancellationToken cancellationToken); Task> DeleteVsDuelAsync(Guid vsDuelId, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/Application/Profiles/AdmonitionProfile.cs b/Application/Profiles/AdmonitionProfile.cs index 5cdcf9a..71ab459 100644 --- a/Application/Profiles/AdmonitionProfile.cs +++ b/Application/Profiles/AdmonitionProfile.cs @@ -10,8 +10,11 @@ public class AdmonitionProfile : Profile { CreateMap(); - CreateMap(); + CreateMap() + .ForMember(des => des.Id, opt => opt.MapFrom(src => Guid.CreateVersion7())) + .ForMember(des => des.CreatedOn, opt => opt.MapFrom(src => DateTime.Now)); - CreateMap(); + CreateMap() + .ForMember(des => des.ModifiedOn, opt => opt.MapFrom(src => DateTime.Now)); } } \ No newline at end of file diff --git a/Application/Profiles/AllianceProfile.cs b/Application/Profiles/AllianceProfile.cs index 3b86147..9930c1c 100644 --- a/Application/Profiles/AllianceProfile.cs +++ b/Application/Profiles/AllianceProfile.cs @@ -11,11 +11,12 @@ public class AllianceProfile : Profile { CreateMap(); - CreateMap(); + CreateMap() + .ForMember(des => des.ModifiedOn, opt => opt.MapFrom(src => DateTime.Now)); - CreateMap(); - - CreateMap() + CreateMap() + .ForMember(des => des.Id, opt => opt.MapFrom(src => Guid.CreateVersion7())) + .ForMember(des => des.CreatedOn, opt => opt.MapFrom(src => DateTime.Now)) .ForMember(des => des.Name, opt => opt.MapFrom(src => src.AllianceName)) .ForMember(des => des.Abbreviation, opt => opt.MapFrom(src => src.AllianceAbbreviation)) .ForMember(des => des.Server, opt => opt.MapFrom(src => src.AllianceServer)); diff --git a/Application/Profiles/AuthenticationProfile.cs b/Application/Profiles/AuthenticationProfile.cs index ed7af54..8882dd4 100644 --- a/Application/Profiles/AuthenticationProfile.cs +++ b/Application/Profiles/AuthenticationProfile.cs @@ -8,7 +8,12 @@ public class AuthenticationProfile : Profile { public AuthenticationProfile() { - CreateMap() + CreateMap() + .ForMember(des => des.Id, opt => opt.MapFrom(src => Guid.CreateVersion7())) + .ForMember(des => des.UserName, opt => opt.MapFrom(src => src.Email)) + .ForMember(des => des.NormalizedEmail, opt => opt.MapFrom(src => src.Email.ToUpper())); + + CreateMap() .ForMember(des => des.UserName, opt => opt.MapFrom(src => src.Email)) .ForMember(des => des.NormalizedEmail, opt => opt.MapFrom(src => src.Email.ToUpper())); } diff --git a/Application/Profiles/CustomEventParticipantProfile.cs b/Application/Profiles/CustomEventParticipantProfile.cs new file mode 100644 index 0000000..15af4c6 --- /dev/null +++ b/Application/Profiles/CustomEventParticipantProfile.cs @@ -0,0 +1,15 @@ +using Application.DataTransferObjects.CustomEventParticipant; +using AutoMapper; +using Database.Entities; + +namespace Application.Profiles; + +public class CustomEventParticipantProfile : Profile +{ + public CustomEventParticipantProfile() + { + CreateMap() + .ForMember(des => des.Id, opt => opt.MapFrom(src => Guid.CreateVersion7())) + .ForMember(des => des.PlayerName, opt => opt.MapFrom(src => src.Player.PlayerName)); + } +} \ No newline at end of file diff --git a/Application/Profiles/CustomEventProfile.cs b/Application/Profiles/CustomEventProfile.cs new file mode 100644 index 0000000..c2da4e9 --- /dev/null +++ b/Application/Profiles/CustomEventProfile.cs @@ -0,0 +1,21 @@ +using Application.DataTransferObjects.CustomEvent; +using AutoMapper; +using Database.Entities; + +namespace Application.Profiles; + +public class CustomEventProfile : Profile +{ + public CustomEventProfile() + { + CreateMap(); + + CreateMap() + .ForMember(des => des.Id, opt => opt.MapFrom(src => Guid.CreateVersion7())) + .ForMember(des => des.EventDate, opt => opt.MapFrom(src => DateTime.Parse(src.EventDateString))); + + CreateMap() + .ForMember(des => des.ModifiedOn, opt => opt.MapFrom(src => DateTime.Now)) + .ForMember(des => des.EventDate, opt => opt.MapFrom(src => DateTime.Parse(src.EventDateString))); + } +} \ No newline at end of file diff --git a/Application/Profiles/DesertStormParticipantProfile.cs b/Application/Profiles/DesertStormParticipantProfile.cs new file mode 100644 index 0000000..6e5237a --- /dev/null +++ b/Application/Profiles/DesertStormParticipantProfile.cs @@ -0,0 +1,19 @@ +using Application.DataTransferObjects.DesertStormParticipants; +using AutoMapper; +using Database.Entities; + +namespace Application.Profiles; + +public class DesertStormParticipantProfile : Profile +{ + public DesertStormParticipantProfile() + { + CreateMap() + .ForMember(des => des.PlayerName, opt => opt.MapFrom(src => src.Player.PlayerName)); + + CreateMap() + .ForMember(des => des.Id, opt => opt.MapFrom(src => Guid.CreateVersion7())); + + CreateMap(); + } +} \ No newline at end of file diff --git a/Application/Profiles/DesertStormProfile.cs b/Application/Profiles/DesertStormProfile.cs index 8dbb479..69a909e 100644 --- a/Application/Profiles/DesertStormProfile.cs +++ b/Application/Profiles/DesertStormProfile.cs @@ -8,12 +8,20 @@ public class DesertStormProfile : Profile { public DesertStormProfile() { - CreateMap(); + CreateMap() + .ForMember(des => des.Participants, + opt => opt.MapFrom(src => src.DesertStormParticipants.Count(p => p.Participated))); - CreateMap(); + CreateMap() + .ForMember(des => des.DesertStormParticipants, opt => opt.MapFrom(src => src.DesertStormParticipants)) + .ForMember(des => des.Participants, + opt => opt.MapFrom(src => src.DesertStormParticipants.Count(p => p.Participated))); + + CreateMap() + .ForMember(des => des.ModifiedOn, opt => opt.MapFrom(src => DateTime.Now)); CreateMap() - .ForMember(des => des.Year, opt => opt.MapFrom(src => DateTime.Now.Year)) - .ForMember(des => des.CalendarWeek, opt => opt.MapFrom(src => DateTime.Now.DayOfWeek)); + .ForMember(des => des.Id, opt => opt.MapFrom(src => Guid.CreateVersion7())) + .ForMember(des => des.EventDate, opt => opt.MapFrom(src => DateTime.Parse(src.EventDate))); } } \ No newline at end of file diff --git a/Application/Profiles/MarshalGuardParticipantProfile.cs b/Application/Profiles/MarshalGuardParticipantProfile.cs new file mode 100644 index 0000000..d1d7f75 --- /dev/null +++ b/Application/Profiles/MarshalGuardParticipantProfile.cs @@ -0,0 +1,19 @@ +using Application.DataTransferObjects.MarshalGuardParticipant; +using AutoMapper; +using Database.Entities; + +namespace Application.Profiles; + +public class MarshalGuardParticipantProfile : Profile +{ + public MarshalGuardParticipantProfile() + { + CreateMap() + .ForMember(des => des.PlayerName, opt => opt.MapFrom(src => src.Player.PlayerName)); + + CreateMap() + .ForMember(des => des.Id, opt => opt.MapFrom(src => Guid.CreateVersion7())); + + CreateMap(); + } +} \ No newline at end of file diff --git a/Application/Profiles/MarshalGuardProfile.cs b/Application/Profiles/MarshalGuardProfile.cs index a6f2fa2..7e91b40 100644 --- a/Application/Profiles/MarshalGuardProfile.cs +++ b/Application/Profiles/MarshalGuardProfile.cs @@ -8,13 +8,19 @@ public class MarshalGuardProfile : Profile { public MarshalGuardProfile() { - CreateMap(); + CreateMap() + .ForMember(des => des.Participants, opt => opt.MapFrom(src => src.MarshalGuardParticipants.Count(p => p.Participated))); - CreateMap(); + CreateMap() + .ForMember(des => des.Participants, opt => opt.MapFrom(src => src.MarshalGuardParticipants.Count(p => p.Participated))) + .ForMember(des => des.MarshalGuardParticipants, opt => opt.MapFrom(des => des.MarshalGuardParticipants)); + + CreateMap() + .ForMember(des => des.EventDate, opt => opt.MapFrom(src => DateTime.Parse(src.EventDate))) + .ForMember(des => des.ModifiedOn, opt => opt.MapFrom(src => DateTime.Now)); CreateMap() - .ForMember(des => des.Year, opt => opt.MapFrom(src => DateTime.Now.Year)) - .ForMember(des => des.Month, opt => opt.MapFrom(src => DateTime.Now.Month)) - .ForMember(des => des.Day, opt => opt.MapFrom(src => DateTime.Now.Day)); + .ForMember(des => des.Id, opt => opt.MapFrom(src => Guid.CreateVersion7())) + .ForMember(des => des.EventDate, opt => opt.MapFrom(src => DateTime.Parse(src.EventDate))); } } \ No newline at end of file diff --git a/Application/Profiles/NoteProfile.cs b/Application/Profiles/NoteProfile.cs index f521fc4..e182db6 100644 --- a/Application/Profiles/NoteProfile.cs +++ b/Application/Profiles/NoteProfile.cs @@ -10,8 +10,11 @@ public class NoteProfile : Profile { CreateMap(); - CreateMap(); + CreateMap() + .ForMember(des => des.ModifiedOn, opt => opt.MapFrom(src => DateTime.Now)); - CreateMap(); + CreateMap() + .ForMember(des => des.Id, opt => opt.MapFrom(src => Guid.CreateVersion7())) + .ForMember(des => des.CreatedOn, opt => opt.MapFrom(src => DateTime.Now)); } } \ No newline at end of file diff --git a/Application/Profiles/PlayerProfile.cs b/Application/Profiles/PlayerProfile.cs index bad8e78..a37ffd9 100644 --- a/Application/Profiles/PlayerProfile.cs +++ b/Application/Profiles/PlayerProfile.cs @@ -9,10 +9,15 @@ public class PlayerProfile : Profile public PlayerProfile() { CreateMap() + .ForMember(des => des.NotesCount, opt => opt.MapFrom(src => src.Notes.Count)) + .ForMember(des => des.AdmonitionsCount, opt => opt.MapFrom(src => src.Admonitions.Count)) .ForMember(des => des.RankName, opt => opt.MapFrom(src => src.Rank.Name)); - CreateMap(); + CreateMap() + .ForMember(des => des.Id, opt => opt.MapFrom(src => Guid.CreateVersion7())) + .ForMember(des => des.CreatedOn, opt => opt.MapFrom(src => DateTime.Now)); - CreateMap(); + CreateMap() + .ForMember(des => des.ModifiedOn, opt => opt.MapFrom(src => DateTime.Now)); } } \ No newline at end of file diff --git a/Application/Profiles/RankProfile.cs b/Application/Profiles/RankProfile.cs new file mode 100644 index 0000000..d9e8db6 --- /dev/null +++ b/Application/Profiles/RankProfile.cs @@ -0,0 +1,13 @@ +using Application.DataTransferObjects.Rank; +using AutoMapper; +using Database.Entities; + +namespace Application.Profiles; + +public class RankProfile : Profile +{ + public RankProfile() + { + CreateMap(); + } +} \ No newline at end of file diff --git a/Application/Profiles/VsDuelParticipantProfile.cs b/Application/Profiles/VsDuelParticipantProfile.cs new file mode 100644 index 0000000..2cce107 --- /dev/null +++ b/Application/Profiles/VsDuelParticipantProfile.cs @@ -0,0 +1,16 @@ +using Application.DataTransferObjects.VsDuelParticipant; +using AutoMapper; +using Database.Entities; + +namespace Application.Profiles; + +public class VsDuelParticipantProfile : Profile +{ + public VsDuelParticipantProfile() + { + CreateMap() + .ForMember(des => des.PlayerName, opt => opt.MapFrom(src => src.Player.PlayerName)); + + CreateMap(); + } +} \ No newline at end of file diff --git a/Application/Profiles/VsDuelProfile.cs b/Application/Profiles/VsDuelProfile.cs index 6b33689..25231a2 100644 --- a/Application/Profiles/VsDuelProfile.cs +++ b/Application/Profiles/VsDuelProfile.cs @@ -10,10 +10,14 @@ public class VsDuelProfile : Profile { CreateMap(); - CreateMap(); + CreateMap(); + + CreateMap() + .ForMember(des => des.ModifiedOn, opt => opt.MapFrom(src => DateTime.Now)) + .ForMember(des => des.EventDate, opt => opt.MapFrom(src => DateTime.Parse(src.EventDate))); CreateMap() - .ForMember(des => des.Year, opt => opt.MapFrom(src => DateTime.Now.Year)) - .ForMember(des => des.CalendarWeek, opt => opt.MapFrom(src => DateTime.Now.DayOfWeek)); + .ForMember(des => des.Id, opt => opt.MapFrom(src => Guid.CreateVersion7())) + .ForMember(des => des.EventDate, opt => opt.MapFrom(src => DateTime.Parse(src.EventDate))); } } \ No newline at end of file diff --git a/Application/Repositories/AdmonitionRepository.cs b/Application/Repositories/AdmonitionRepository.cs index 37ed5b4..b3e7e76 100644 --- a/Application/Repositories/AdmonitionRepository.cs +++ b/Application/Repositories/AdmonitionRepository.cs @@ -29,6 +29,7 @@ public class AdmonitionRepository(ApplicationContext context, IMapper mapper, IL .Where(admonition => admonition.PlayerId == playerId) .ProjectTo(mapper.ConfigurationProvider) .AsNoTracking() + .OrderByDescending(admonition => admonition.CreatedOn) .ToListAsync(cancellationToken); return Result.Success(playerAdmonitions); @@ -46,9 +47,10 @@ public class AdmonitionRepository(ApplicationContext context, IMapper mapper, IL : Result.Success(admonitionById); } - public async Task> CreateAdmonitionAsync(CreateAdmonitionDto createAdmonitionDto, CancellationToken cancellationToken) + public async Task> CreateAdmonitionAsync(CreateAdmonitionDto createAdmonitionDto, string createdBy, CancellationToken cancellationToken) { var newAdmonition = mapper.Map(createAdmonitionDto); + newAdmonition.CreatedBy = createdBy; await context.Admonitions.AddAsync(newAdmonition, cancellationToken); @@ -65,7 +67,7 @@ public class AdmonitionRepository(ApplicationContext context, IMapper mapper, IL } } - public async Task> UpdateAdmonitionAsync(UpdateAdmonitionDto updateAdmonitionDto, CancellationToken cancellationToken) + public async Task> UpdateAdmonitionAsync(UpdateAdmonitionDto updateAdmonitionDto, string modifiedBy, CancellationToken cancellationToken) { var admonitionToUpdate = await context.Admonitions .FirstOrDefaultAsync(admonition => admonition.Id == updateAdmonitionDto.Id, cancellationToken); @@ -73,6 +75,7 @@ public class AdmonitionRepository(ApplicationContext context, IMapper mapper, IL if (admonitionToUpdate is null) return Result.Failure(AdmonitionErrors.NotFound); mapper.Map(updateAdmonitionDto, admonitionToUpdate); + admonitionToUpdate.ModifiedBy = modifiedBy; try { diff --git a/Application/Repositories/AllianceRepository.cs b/Application/Repositories/AllianceRepository.cs index ee0f2c8..a35ed53 100644 --- a/Application/Repositories/AllianceRepository.cs +++ b/Application/Repositories/AllianceRepository.cs @@ -34,17 +34,7 @@ public class AllianceRepository(ApplicationContext context, IMapper mapper) : IA : Result.Success(allianceById); } - public async Task> CreateAllianceAsync(CreateAllianceDto createAllianceDto, CancellationToken cancellationToken) - { - var newAlliance = mapper.Map(createAllianceDto); - - await context.Alliances.AddAsync(newAlliance, cancellationToken); - await context.SaveChangesAsync(cancellationToken); - - return Result.Success(mapper.Map(newAlliance)); - } - - public async Task> UpdateAllianceAsync(UpdateAllianceDto updateAllianceDto, CancellationToken cancellationToken) + public async Task> UpdateAllianceAsync(UpdateAllianceDto updateAllianceDto, string modifiedBy, CancellationToken cancellationToken) { var allianceToUpdate = await context.Alliances .FirstOrDefaultAsync(alliance => alliance.Id == updateAllianceDto.Id, cancellationToken); @@ -52,6 +42,7 @@ public class AllianceRepository(ApplicationContext context, IMapper mapper) : IA if (allianceToUpdate is null) return Result.Failure(AllianceErrors.NotFound); mapper.Map(updateAllianceDto, allianceToUpdate); + allianceToUpdate.ModifiedBy = modifiedBy; await context.SaveChangesAsync(cancellationToken); diff --git a/Application/Repositories/AuthenticationRepository.cs b/Application/Repositories/AuthenticationRepository.cs index bc78105..a0de9e5 100644 --- a/Application/Repositories/AuthenticationRepository.cs +++ b/Application/Repositories/AuthenticationRepository.cs @@ -2,6 +2,8 @@ using Application.Classes; using Application.DataTransferObjects.Authentication; using Application.Errors; +using Application.Helpers; +using Application.Helpers.Email; using Application.Interfaces; using AutoMapper; using Database; @@ -9,11 +11,13 @@ using Database.Entities; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; +using Utilities.Classes; using Utilities.Constants; +using Utilities.Interfaces; namespace Application.Repositories; -public class AuthenticationRepository(UserManager userManager, ApplicationContext context, IMapper mapper, IJwtService jwtService, ILogger logger) : IAuthenticationRepository +public class AuthenticationRepository(UserManager userManager, ApplicationContext context, IMapper mapper, IJwtService jwtService, ILogger logger, IEmailService emailService) : IAuthenticationRepository { public async Task> LoginAsync(LoginRequestDto loginRequestDto, CancellationToken cancellationToken) { @@ -24,6 +28,11 @@ public class AuthenticationRepository(UserManager userManager, Application return Result.Failure(AuthenticationErrors.LoginFailed); } + if (!await userManager.IsEmailConfirmedAsync(userToLogin)) + { + return Result.Failure(AuthenticationErrors.EmailNotConfirmed); + } + var loginResponse = new LoginResponseDto() { Token = await CreateJwtToken(userToLogin) @@ -32,28 +41,190 @@ public class AuthenticationRepository(UserManager userManager, Application return Result.Success(loginResponse); } - public async Task> RegisterToApplicationAsync(RegisterRequestDto registerRequestDto, CancellationToken cancellationToken) + public async Task RegisterUserAsync(RegisterUserDto registerUserDto, CancellationToken cancellationToken) { - var newUser = mapper.Map(registerRequestDto); - var userAlliance = mapper.Map(registerRequestDto); + var allianceForUser = await context.Alliances + .AsNoTracking() + .FirstOrDefaultAsync(alliance => alliance.Id == registerUserDto.AllianceId, cancellationToken); + + if (allianceForUser is null) return Result.Failure(AllianceErrors.NotFound); + + var userRole = await context.Roles + .AsNoTracking() + .FirstOrDefaultAsync(role => role.Id == registerUserDto.RoleId, cancellationToken); + + if (userRole is null) return Result.Failure(RoleErrors.NotFound); + + var newUser = mapper.Map(registerUserDto); + newUser.AllianceId = allianceForUser.Id; + newUser.EmailConfirmed = false; + + var userCreateResult = await userManager.CreateAsync(newUser, registerUserDto.Password); + + if (!userCreateResult.Succeeded) return Result.Failure(AuthenticationErrors.RegisterFailed); + + var addRoleResult = await userManager.AddToRoleAsync(newUser, userRole.Name!); + + if (!addRoleResult.Succeeded) + { + await userManager.DeleteAsync(newUser); + return Result.Failure(AuthenticationErrors.RegisterFailed); + } + + var emailTemplate = EmailTemplateFactory.GetEmailTemplate("en"); + + var emailConfirmToken = await userManager.GenerateEmailConfirmationTokenAsync(newUser); + var callBack = new Uri(registerUserDto.EmailConfirmUri) + .AddQueryParam("token", emailConfirmToken) + .AddQueryParam("email", registerUserDto.Email); + + var confirmEmailContent = emailTemplate.ConfirmEmail(newUser.PlayerName, callBack.ToString()); + + var emailConfirmMessage = new EmailMessage([registerUserDto.Email], confirmEmailContent.Subject, + confirmEmailContent.Content); + + var emailSendResponse = await emailService.SendEmailAsync(emailConfirmMessage); + + if (emailSendResponse) return Result.Success(); + + await userManager.DeleteAsync(newUser); + return Result.Failure(AuthenticationErrors.RegisterFailed); + } + + public async Task EmailConfirmationAsync(ConfirmEmailRequestDto confirmEmailRequestDto) + { + var userToConfirm = await userManager.FindByEmailAsync(confirmEmailRequestDto.Email); + + if (userToConfirm is null) return Result.Failure(UserErrors.NotFound); + + var confirmEmailResult = await userManager.ConfirmEmailAsync(userToConfirm, confirmEmailRequestDto.Token); + + return !confirmEmailResult.Succeeded ? Result.Failure(AuthenticationErrors.EmailNotConfirmed) : Result.Success(); + } + + public async Task ResendConfirmationEmailAsync(EmailConfirmationRequestDto emailConfirmationRequestDto) + { + var user = await userManager.FindByEmailAsync(emailConfirmationRequestDto.Email); + + if (user is null) return Result.Failure(UserErrors.NotFound); + + var token = await userManager.GenerateEmailConfirmationTokenAsync(user); + + var callback = new Uri(emailConfirmationRequestDto.ClientUri) + .AddQueryParam("token", token) + .AddQueryParam("email", emailConfirmationRequestDto.Email); + + var emailTemplate = EmailTemplateFactory.GetEmailTemplate("en"); + + var emailContent = emailTemplate.ResendConfirmationEmail(user.PlayerName, callback.ToString()); + + var emailMessage = + new EmailMessage([emailConfirmationRequestDto.Email], emailContent.Subject, emailContent.Content); + + var sendMailResponse = await emailService.SendEmailAsync(emailMessage); + + return sendMailResponse + ? Result.Success() + : Result.Failure(AuthenticationErrors.ResendConfirmationEmailFailed); + } + + public async Task InviteUserAsync(InviteUserDto inviteUserDto, CancellationToken cancellationToken) + { + var invitingUser = await userManager.FindByIdAsync(inviteUserDto.InvitingUserId.ToString()); + + if (invitingUser is null) return Result.Failure(UserErrors.NotFound); + + var allianceForUser = await context.Alliances + .AsNoTracking() + .FirstOrDefaultAsync(alliance => alliance.Id == invitingUser.AllianceId, cancellationToken); + + if (allianceForUser is null) return Result.Failure(AllianceErrors.NotFound); + + var roleForUser = await context.Roles + .AsNoTracking() + .FirstOrDefaultAsync(role => role.Name == inviteUserDto.Role, cancellationToken); + + if (roleForUser is null) return Result.Failure(RoleErrors.NotFound); + + var emailTemplate = EmailTemplateFactory.GetEmailTemplate("en"); + + var callBack = new Uri(inviteUserDto.RegisterUserUri) + .AddQueryParam("email", inviteUserDto.Email) + .AddQueryParam("allianceId", inviteUserDto.AllianceId.ToString()) + .AddQueryParam("role", roleForUser.Id.ToString()); + + var inviteUserEmailContent = + emailTemplate.InviteUserEmail(invitingUser.PlayerName, allianceForUser.Name, callBack.ToString()); + + var inviteUserEmailMessage = new EmailMessage([inviteUserDto.Email], inviteUserEmailContent.Subject, + inviteUserEmailContent.Content); + + var emailSendResponse = await emailService.SendEmailAsync(inviteUserEmailMessage); + + return emailSendResponse ? Result.Success() : Result.Failure(AuthenticationErrors.InviteUserFailed); + } + + public async Task ResetPasswordAsync(ResetPasswordDto resetPasswordDto) + { + var user = await userManager.FindByEmailAsync(resetPasswordDto.Email); + + if (user is null) return Result.Failure(UserErrors.NotFound); + + var resetPasswordResult = + await userManager.ResetPasswordAsync(user, resetPasswordDto.Token, resetPasswordDto.Password); + + return resetPasswordResult.Succeeded + ? Result.Success() + : Result.Failure(new Error("Error.ResetPassword.Failed", "Reset password failed")); + } + + public async Task ForgotPasswordAsync(ForgotPasswordDto forgotPasswordDto) + { + var user = await userManager.FindByEmailAsync(forgotPasswordDto.Email); + + if (user is null) return Result.Failure(UserErrors.NotFound); + + var resetPasswordToken = await userManager.GeneratePasswordResetTokenAsync(user); + + var emailTemplate = EmailTemplateFactory.GetEmailTemplate("en"); + + var callback = new Uri(forgotPasswordDto.ResetPasswordUri) + .AddQueryParam("token", resetPasswordToken) + .AddQueryParam("email", forgotPasswordDto.Email); + + var resetPasswordContent = emailTemplate.ResetPasswordEmail(user.PlayerName, callback.ToString()); + + var resetPasswordEmailMessage = new EmailMessage([forgotPasswordDto.Email], resetPasswordContent.Subject, + resetPasswordContent.Content); + + var emailSendResponse = await emailService.SendEmailAsync(resetPasswordEmailMessage); + + return emailSendResponse ? Result.Success() : Result.Failure(new Error("Error.ResetPassword.SendMail", "Could not send the email")); + } + + public async Task RegisterToApplicationAsync(SignUpRequestDto signUpRequestDto, CancellationToken cancellationToken) + { + var newUser = mapper.Map(signUpRequestDto); + var userAlliance = mapper.Map(signUpRequestDto); var checkAllianceExists = await AllianceAlreadyExists(userAlliance.Server, userAlliance.Abbreviation, cancellationToken); - if (checkAllianceExists) return Result.Failure(AuthenticationErrors.AllianceAlreadyExists); + if (checkAllianceExists) return Result.Failure(AuthenticationErrors.AllianceAlreadyExists); var createAllianceResult = await CreateAlliance(userAlliance, cancellationToken); - if (createAllianceResult.IsFailure) return Result.Failure(createAllianceResult.Error); + if (createAllianceResult.IsFailure) return Result.Failure(createAllianceResult.Error); newUser.AllianceId = createAllianceResult.Value.Id; + newUser.EmailConfirmed = false; - var userCreateResult = await userManager.CreateAsync(newUser, registerRequestDto.Password); + var userCreateResult = await userManager.CreateAsync(newUser, signUpRequestDto.Password); if (!userCreateResult.Succeeded) { var rollBackResult = await RollbackAlliance(createAllianceResult.Value, cancellationToken); - return Result.Failure(rollBackResult.IsFailure ? rollBackResult.Error : AuthenticationErrors.RegisterFailed); + return Result.Failure(rollBackResult.IsFailure ? rollBackResult.Error : AuthenticationErrors.RegisterFailed); } var addRoleResult = await userManager.AddToRoleAsync(newUser, ApplicationRoles.Administrator); @@ -62,15 +233,28 @@ public class AuthenticationRepository(UserManager userManager, Application { await userManager.DeleteAsync(newUser); await RollbackAlliance(createAllianceResult.Value, cancellationToken); - return Result.Failure(AuthenticationErrors.RegisterFailed); + return Result.Failure(AuthenticationErrors.RegisterFailed); } - var response = new LoginResponseDto() - { - Token = await CreateJwtToken(newUser) - }; + var emailTemplate = EmailTemplateFactory.GetEmailTemplate("en"); - return Result.Success(response); + var emailConfirmToken = await userManager.GenerateEmailConfirmationTokenAsync(newUser); + var callBack = new Uri(signUpRequestDto.EmailConfirmUri) + .AddQueryParam("token", emailConfirmToken) + .AddQueryParam("email", signUpRequestDto.Email); + + var confirmEmailContent = emailTemplate.ConfirmEmail(newUser.PlayerName, callBack.ToString()); + + var emailConfirmMessage = new EmailMessage([signUpRequestDto.Email], confirmEmailContent.Subject, + confirmEmailContent.Content); + + var emailSendResponse = await emailService.SendEmailAsync(emailConfirmMessage); + + if (emailSendResponse) return Result.Success(); + + await userManager.DeleteAsync(newUser); + await RollbackAlliance(createAllianceResult.Value, cancellationToken); + return Result.Failure(AuthenticationErrors.RegisterFailed); } private async Task> CreateAlliance(Alliance alliance, CancellationToken cancellationToken) @@ -117,7 +301,7 @@ public class AuthenticationRepository(UserManager userManager, Application private async Task AllianceAlreadyExists(int server, string allianceAbbreviation, CancellationToken cancellationToken) { var allianceToCheck = await context.Alliances - .FirstOrDefaultAsync(alliance => alliance.Server == server && alliance.Abbreviation == allianceAbbreviation, cancellationToken); + .FirstOrDefaultAsync(alliance => alliance.Server == server && alliance.Abbreviation.ToLower() == allianceAbbreviation.ToLower(), cancellationToken); return allianceToCheck is not null; } diff --git a/Application/Repositories/CustomEventRepository.cs b/Application/Repositories/CustomEventRepository.cs new file mode 100644 index 0000000..5eb9d67 --- /dev/null +++ b/Application/Repositories/CustomEventRepository.cs @@ -0,0 +1,119 @@ +using Application.Classes; +using Application.DataTransferObjects.CustomEvent; +using Application.Errors; +using Application.Interfaces; +using AutoMapper; +using AutoMapper.QueryableExtensions; +using Database; +using Database.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace Application.Repositories; + +public class CustomEventRepository(ApplicationContext context, IMapper mapper, ILogger logger) : ICustomEventRepository +{ + public async Task> GetCustomEventAsync(Guid customEventId, CancellationToken cancellationToken) + { + var customEventById = await context.CustomEvents + .ProjectTo(mapper.ConfigurationProvider) + .AsNoTracking() + .FirstOrDefaultAsync(customEvent => customEvent.Id == customEventId, cancellationToken); + + return customEventById is null + ? Result.Failure(CustomEventErrors.NotFound) + : Result.Success(customEventById); + } + + public async Task> GetCustomEventDetailAsync(Guid customEventId, CancellationToken cancellationToken) + { + var customEventDetail = await context.CustomEvents + .ProjectTo(mapper.ConfigurationProvider) + .AsNoTracking() + .FirstOrDefaultAsync(customEvent => customEvent.Id == customEventId, cancellationToken); + + return customEventDetail is null + ? Result.Failure(CustomEventErrors.NotFound) + : Result.Success(customEventDetail); + } + + public async Task>> GetAllianceCustomEventsAsync(Guid allianceId, int take, CancellationToken cancellationToken) + { + var allianceCustomEvents = await context.CustomEvents + .Where(customEvent => customEvent.AllianceId == allianceId) + .ProjectTo(mapper.ConfigurationProvider) + .AsNoTracking() + .OrderByDescending(customEvent => customEvent.EventDate) + .Take(take) + .ToListAsync(cancellationToken); + + return Result.Success(allianceCustomEvents); + } + + public async Task> CreateCustomEventAsync(CreateCustomEventDto createCustomEventDto, string createdBy, + CancellationToken cancellationToken) + { + var newCustomEvent = mapper.Map(createCustomEventDto); + + newCustomEvent.CreatedBy = createdBy; + + await context.CustomEvents.AddAsync(newCustomEvent, cancellationToken); + + try + { + await context.SaveChangesAsync(cancellationToken); + return Result.Success(mapper.Map(newCustomEvent)); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return Result.Failure(GeneralErrors.DatabaseError); + } + + } + + public async Task> UpdateCustomEventAsync(UpdateCustomEventDto updateCustomEventDto, string modifiedBy, + CancellationToken cancellationToken) + { + var customEventToUpdate = + await context.CustomEvents.FirstOrDefaultAsync(customEvent => customEvent.Id == updateCustomEventDto.Id, + cancellationToken); + + if (customEventToUpdate is null) return Result.Failure(CustomEventErrors.NotFound); + + mapper.Map(updateCustomEventDto, customEventToUpdate); + customEventToUpdate.ModifiedBy = modifiedBy; + + try + { + await context.SaveChangesAsync(cancellationToken); + return Result.Success(mapper.Map(customEventToUpdate)); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return Result.Failure(GeneralErrors.DatabaseError); + } + } + + public async Task> DeleteCustomEventAsync(Guid customEventId, CancellationToken cancellationToken) + { + var customEventToDelete = + await context.CustomEvents.FirstOrDefaultAsync(customEvent => customEvent.Id == customEventId, cancellationToken); + + if (customEventToDelete is null) return Result.Failure(CustomEventErrors.NotFound); + + context.CustomEvents.Remove(customEventToDelete); + + try + { + await context.SaveChangesAsync(cancellationToken); + return Result.Success(mapper.Map(true)); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return Result.Failure(GeneralErrors.DatabaseError); + } + } +} \ No newline at end of file diff --git a/Application/Repositories/DesertStormParticipantRepository.cs b/Application/Repositories/DesertStormParticipantRepository.cs new file mode 100644 index 0000000..a2b0366 --- /dev/null +++ b/Application/Repositories/DesertStormParticipantRepository.cs @@ -0,0 +1,83 @@ +using Application.Classes; +using Application.DataTransferObjects.DesertStormParticipants; +using Application.Errors; +using Application.Interfaces; +using AutoMapper; +using AutoMapper.QueryableExtensions; +using Database; +using Database.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace Application.Repositories; + +public class DesertStormParticipantRepository(ApplicationContext context, ILogger logger, IMapper mapper) : IDesertStormParticipantRepository +{ + public async Task> GetDesertStormParticipantAsync(Guid desertStormParticipantId, CancellationToken cancellationToken) + { + var desertStormParticipantById = await context.DesertStormParticipants + .ProjectTo(mapper.ConfigurationProvider) + .AsNoTracking() + .FirstOrDefaultAsync(desertStormParticipant => desertStormParticipant.Id == desertStormParticipantId, + cancellationToken); + + return desertStormParticipantById is null + ? Result.Failure(new Error("", "")) + : Result.Success(desertStormParticipantById); + } + + public async Task> InsertDesertStormParticipantAsync(List createDesertStormParticipants, CancellationToken cancellationToken) + { + var desertStormParticipants = mapper.Map>(createDesertStormParticipants); + + await context.DesertStormParticipants.AddRangeAsync(desertStormParticipants, cancellationToken); + + try + { + await context.SaveChangesAsync(cancellationToken); + return Result.Success(true); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return Result.Failure(GeneralErrors.DatabaseError); + } + } + + public async Task>> GetPlayerDesertStormParticipantsAsync(Guid playerId, int last, CancellationToken cancellationToken) + { + var desertStormPlayerParticipated = await context.DesertStormParticipants + .Where(desertStormParticipant => desertStormParticipant.PlayerId == playerId) + .OrderByDescending(desertStormParticipant => desertStormParticipant.DesertStorm.EventDate) + .Take(last) + .ProjectTo(mapper.ConfigurationProvider) + .AsNoTracking() + .ToListAsync(cancellationToken); + + return Result.Success(desertStormPlayerParticipated); + } + + public async Task> UpdateDesertStormParticipantAsync(UpdateDesertStormParticipantDto updateDesertStormParticipantDto, + CancellationToken cancellationToken) + { + var desertStormParticipantToUpdate = await context.DesertStormParticipants + .FirstOrDefaultAsync( + desertStormParticipant => desertStormParticipant.Id == updateDesertStormParticipantDto.Id, + cancellationToken); + + if (desertStormParticipantToUpdate is null) return Result.Failure(new Error("", "")); + + mapper.Map(updateDesertStormParticipantDto, desertStormParticipantToUpdate); + + try + { + await context.SaveChangesAsync(cancellationToken); + return Result.Success(mapper.Map(desertStormParticipantToUpdate)); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return Result.Failure(GeneralErrors.DatabaseError); + } + } +} \ No newline at end of file diff --git a/Application/Repositories/DesertStormRepository.cs b/Application/Repositories/DesertStormRepository.cs index e4deb21..a40fd21 100644 --- a/Application/Repositories/DesertStormRepository.cs +++ b/Application/Repositories/DesertStormRepository.cs @@ -25,20 +25,35 @@ public class DesertStormRepository(ApplicationContext context, IMapper mapper, I : Result.Success(desertStormById); } - public async Task>> GetPlayerDesertStormsAsync(Guid playerId, CancellationToken cancellationToken) + public async Task>> GetAllianceDesertStormsAsync(Guid allianceId, int take, CancellationToken cancellationToken) { - var playerDesertStorms = await context.DesertStorms - .Where(desertStorm => desertStorm.PlayerId == playerId) + var allianceDesertStorms = await context.DesertStorms + .Where(desertStorm => desertStorm.AllianceId == allianceId) .ProjectTo(mapper.ConfigurationProvider) .AsNoTracking() + .OrderByDescending(desertStorm => desertStorm.EventDate) + .Take(take) .ToListAsync(cancellationToken); - return Result.Success(playerDesertStorms); + return Result.Success(allianceDesertStorms); } - public async Task> CreateDesertStormAsync(CreateDesertStormDto createDesertStormDto, CancellationToken cancellationToken) + public async Task> GetDesertStormDetailAsync(Guid desertStormId, CancellationToken cancellationToken) + { + var desertStormDetail = await context.DesertStorms + .ProjectTo(mapper.ConfigurationProvider) + .AsNoTracking() + .FirstOrDefaultAsync(desertStorm => desertStorm.Id == desertStormId, cancellationToken); + + return desertStormDetail is null + ? Result.Failure(DesertStormErrors.NotFound) + : Result.Success(desertStormDetail); + } + + public async Task> CreateDesertStormAsync(CreateDesertStormDto createDesertStormDto, string createdBy, CancellationToken cancellationToken) { var newDesertStorm = mapper.Map(createDesertStormDto); + newDesertStorm.CreatedBy = createdBy; await context.DesertStorms.AddAsync(newDesertStorm, cancellationToken); @@ -54,7 +69,7 @@ public class DesertStormRepository(ApplicationContext context, IMapper mapper, I } } - public async Task> UpdateDesertStormAsync(UpdateDesertStormDto updateDesertStormDto, CancellationToken cancellationToken) + public async Task> UpdateDesertStormAsync(UpdateDesertStormDto updateDesertStormDto, string modifiedBy, CancellationToken cancellationToken) { var desertStormToUpdate = await context.DesertStorms .FirstOrDefaultAsync(desertStorm => desertStorm.Id == updateDesertStormDto.Id, cancellationToken); @@ -62,6 +77,7 @@ public class DesertStormRepository(ApplicationContext context, IMapper mapper, I if (desertStormToUpdate is null) return Result.Failure(DesertStormErrors.NotFound); mapper.Map(updateDesertStormDto, desertStormToUpdate); + desertStormToUpdate.ModifiedBy = modifiedBy; try { diff --git a/Application/Repositories/MarshalGuardParticipantRepository.cs b/Application/Repositories/MarshalGuardParticipantRepository.cs new file mode 100644 index 0000000..10e26bf --- /dev/null +++ b/Application/Repositories/MarshalGuardParticipantRepository.cs @@ -0,0 +1,83 @@ +using Application.Classes; +using Application.DataTransferObjects.MarshalGuardParticipant; +using Application.Errors; +using Application.Interfaces; +using AutoMapper; +using AutoMapper.QueryableExtensions; +using Database; +using Database.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace Application.Repositories; + +public class MarshalGuardParticipantRepository(ApplicationContext context, IMapper mapper, ILogger logger) : IMarshalGuardParticipantRepository +{ + public async Task> GetMarshalGuardParticipantAsync(Guid marshalGuardParticipantId, CancellationToken cancellationToken) + { + var marshalGuardParticipantById = await context.MarshalGuardParticipants + .ProjectTo(mapper.ConfigurationProvider) + .AsNoTracking() + .FirstOrDefaultAsync(marshalGuardParticipant => marshalGuardParticipant.Id == marshalGuardParticipantId, + cancellationToken); + return marshalGuardParticipantById is null + ? Result.Failure(new Error("", "")) + : Result.Success(marshalGuardParticipantById); + } + + public async Task> InsertMarshalGuardParticipantAsync(List createMarshalGuardParticipantsDto, + CancellationToken cancellationToken) + { + var newMarshalGuardParticipants = mapper.Map>(createMarshalGuardParticipantsDto); + + await context.AddRangeAsync(newMarshalGuardParticipants, cancellationToken); + + try + { + await context.SaveChangesAsync(cancellationToken); + return Result.Success(true); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return Result.Failure(GeneralErrors.DatabaseError); + } + } + + public async Task>> GetPlayerMarshalParticipantsAsync(Guid playerId, int last, CancellationToken cancellationToken) + { + var playerMarshalParticipants = await context.MarshalGuardParticipants + .Where(mp => mp.PlayerId == playerId) + .OrderByDescending(mp => mp.MarshalGuard.EventDate) + .Take(last) + .ProjectTo(mapper.ConfigurationProvider) + .AsNoTracking() + .ToListAsync(cancellationToken); + + return Result.Success(playerMarshalParticipants); + } + + public async Task> UpdateMarshalGuardParticipantAsync(UpdateMarshalGuardParticipantDto updateMarshalGuardParticipantDto, + CancellationToken cancellationToken) + { + var participantToUpdate = await context.MarshalGuardParticipants + .FirstOrDefaultAsync( + marshalGuardParticipant => marshalGuardParticipant.Id == updateMarshalGuardParticipantDto.Id, + cancellationToken); + + if (participantToUpdate is null) return Result.Failure(MarshalGuardErrors.NotFound); + + mapper.Map(updateMarshalGuardParticipantDto, participantToUpdate); + + try + { + await context.SaveChangesAsync(cancellationToken); + return Result.Success(mapper.Map(participantToUpdate)); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return Result.Failure(GeneralErrors.DatabaseError); + } + } +} \ No newline at end of file diff --git a/Application/Repositories/MarshalGuardRepository.cs b/Application/Repositories/MarshalGuardRepository.cs index 043a312..7150a1e 100644 --- a/Application/Repositories/MarshalGuardRepository.cs +++ b/Application/Repositories/MarshalGuardRepository.cs @@ -25,20 +25,36 @@ public class MarshalGuardRepository(ApplicationContext context, IMapper mapper, : Result.Success(marshalGuardById); } - public async Task>> GetPlayerMarshalGuardsAsync(Guid playerId, CancellationToken cancellationToken) + public async Task> GetMarshalGuardDetailAsync(Guid marshalGuardId, CancellationToken cancellationToken) { - var playerMarshalGuards = await context.MarshalGuards - .Where(marshalGuard => marshalGuard.PlayerId == playerId) - .ProjectTo(mapper.ConfigurationProvider) + var detailMarshalGuard = await context.MarshalGuards + .ProjectTo(mapper.ConfigurationProvider) .AsNoTracking() - .ToListAsync(cancellationToken); + .FirstOrDefaultAsync(marshalGuard => marshalGuard.Id == marshalGuardId, cancellationToken); - return Result.Success(playerMarshalGuards); + return detailMarshalGuard is null + ? Result.Failure(MarshalGuardErrors.NotFound) + : Result.Success(detailMarshalGuard); } - public async Task> CreateMarshalGuardAsync(CreateMarshalGuardDto createMarshalGuardDto, CancellationToken cancellationToken) + public async Task>> GetAllianceMarshalGuardsAsync(Guid allianceId, int take, CancellationToken cancellationToken) + { + var allianceMarshalGuards = await context.MarshalGuards + .Where(marshalGuard => marshalGuard.AllianceId == allianceId) + .OrderByDescending(marshalGuard => marshalGuard.EventDate) + .ProjectTo(mapper.ConfigurationProvider) + .AsNoTracking() + .Take(take) + .ToListAsync(cancellationToken); + + return Result.Success(allianceMarshalGuards); + } + + + public async Task> CreateMarshalGuardsAsync(CreateMarshalGuardDto createMarshalGuardDto, string createdBy, CancellationToken cancellationToken) { var newMarshalGuard = mapper.Map(createMarshalGuardDto); + newMarshalGuard.CreatedBy = createdBy; await context.MarshalGuards.AddAsync(newMarshalGuard, cancellationToken); @@ -54,7 +70,7 @@ public class MarshalGuardRepository(ApplicationContext context, IMapper mapper, } } - public async Task> UpdateMarshalGuardAsync(UpdateMarshalGuardDto updateMarshalGuardDto, CancellationToken cancellationToken) + public async Task> UpdateMarshalGuardAsync(UpdateMarshalGuardDto updateMarshalGuardDto, string modifiedBy, CancellationToken cancellationToken) { var marshalGuardToUpdate = await context.MarshalGuards .FirstOrDefaultAsync(marshalGuard => marshalGuard.Id == updateMarshalGuardDto.Id, cancellationToken); @@ -62,6 +78,7 @@ public class MarshalGuardRepository(ApplicationContext context, IMapper mapper, if (marshalGuardToUpdate is null) return Result.Failure(MarshalGuardErrors.NotFound); mapper.Map(updateMarshalGuardDto, marshalGuardToUpdate); + marshalGuardToUpdate.ModifiedBy = modifiedBy; try { diff --git a/Application/Repositories/NoteRepository.cs b/Application/Repositories/NoteRepository.cs index ebd17d8..879e638 100644 --- a/Application/Repositories/NoteRepository.cs +++ b/Application/Repositories/NoteRepository.cs @@ -31,14 +31,16 @@ public class NoteRepository(ApplicationContext context, IMapper mapper, ILogger< .Where(note => note.PlayerId == playerId) .ProjectTo(mapper.ConfigurationProvider) .AsNoTracking() + .OrderByDescending(note => note.CreatedOn) .ToListAsync(cancellationToken); return Result.Success(playerNotes); } - public async Task> CreateNoteAsync(CreateNoteDto createNoteDto, CancellationToken cancellationToken) + public async Task> CreateNoteAsync(CreateNoteDto createNoteDto, string createdBy, CancellationToken cancellationToken) { var newNote = mapper.Map(createNoteDto); + newNote.CreatedBy = createdBy; await context.Notes.AddAsync(newNote, cancellationToken); @@ -54,7 +56,7 @@ public class NoteRepository(ApplicationContext context, IMapper mapper, ILogger< } } - public async Task> UpdateNoteAsync(UpdateNoteDto updateNoteDto, CancellationToken cancellationToken) + public async Task> UpdateNoteAsync(UpdateNoteDto updateNoteDto, string modifiedBy, CancellationToken cancellationToken) { var noteToUpdate = await context.Notes .FirstOrDefaultAsync(note => note.Id == updateNoteDto.Id, cancellationToken); @@ -62,6 +64,7 @@ public class NoteRepository(ApplicationContext context, IMapper mapper, ILogger< if (noteToUpdate is null) return Result.Failure(NoteErrors.NotFound); mapper.Map(updateNoteDto, noteToUpdate); + noteToUpdate.ModifiedBy = modifiedBy; try { diff --git a/Application/Repositories/PlayerRepository.cs b/Application/Repositories/PlayerRepository.cs index 50b8d88..d971502 100644 --- a/Application/Repositories/PlayerRepository.cs +++ b/Application/Repositories/PlayerRepository.cs @@ -36,9 +36,10 @@ public class PlayerRepository(ApplicationContext context, IMapper mapper, ILogge return Result.Success(alliancePlayers); } - public async Task> CreatePlayerAsync(CreatePlayerDto createPlayerDto, CancellationToken cancellationToken) + public async Task> CreatePlayerAsync(CreatePlayerDto createPlayerDto, string createdBy, CancellationToken cancellationToken) { var newPlayer = mapper.Map(createPlayerDto); + newPlayer.CreatedBy = createdBy; await context.Players.AddAsync(newPlayer, cancellationToken); @@ -54,7 +55,7 @@ public class PlayerRepository(ApplicationContext context, IMapper mapper, ILogge } } - public async Task> UpdatePlayerAsync(UpdatePlayerDto updatePlayerDto, CancellationToken cancellationToken) + public async Task> UpdatePlayerAsync(UpdatePlayerDto updatePlayerDto, string modifiedBy, CancellationToken cancellationToken) { var playerToUpdate = await context.Players .FirstOrDefaultAsync(player => player.Id == updatePlayerDto.Id, cancellationToken); @@ -62,6 +63,7 @@ public class PlayerRepository(ApplicationContext context, IMapper mapper, ILogge if (playerToUpdate is null) return Result.Failure(PlayerErrors.NotFound); mapper.Map(updatePlayerDto, playerToUpdate); + playerToUpdate.ModifiedBy = modifiedBy; try { @@ -82,6 +84,30 @@ public class PlayerRepository(ApplicationContext context, IMapper mapper, ILogge if (playerToDelete is null) return Result.Failure(PlayerErrors.NotFound); + var customEvents = await context.CustomEventParticipants + .Where(customEvent => customEvent.PlayerId == playerToDelete.Id) + .ToListAsync(cancellationToken); + + if (customEvents.Count > 0) context.CustomEventParticipants.RemoveRange(customEvents); + + var desertStorms = await context.DesertStormParticipants + .Where(desertStorm => desertStorm.PlayerId == playerToDelete.Id) + .ToListAsync(cancellationToken); + + if (desertStorms.Count > 0) context.DesertStormParticipants.RemoveRange(desertStorms); + + var vsDuels = await context.VsDuelParticipants + .Where(vsDuel => vsDuel.PlayerId == playerToDelete.Id) + .ToListAsync(cancellationToken); + + if (vsDuels.Count > 0) context.VsDuelParticipants.RemoveRange(vsDuels); + + var marshalGuards = await context.MarshalGuardParticipants + .Where(marshalGuard => marshalGuard.PlayerId == playerToDelete.Id) + .ToListAsync(cancellationToken); + + if (vsDuels.Count > 0) context.MarshalGuardParticipants.RemoveRange(marshalGuards); + context.Players.Remove(playerToDelete); try diff --git a/Application/Repositories/RankRepository.cs b/Application/Repositories/RankRepository.cs new file mode 100644 index 0000000..925dfa8 --- /dev/null +++ b/Application/Repositories/RankRepository.cs @@ -0,0 +1,23 @@ +using Application.Classes; +using Application.DataTransferObjects.Rank; +using Application.Interfaces; +using AutoMapper; +using AutoMapper.QueryableExtensions; +using Database; +using Microsoft.EntityFrameworkCore; + +namespace Application.Repositories; + +public class RankRepository(ApplicationContext context, IMapper mapper) : IRankRepository +{ + public async Task>> GetRanksAsync(CancellationToken cancellationToken) + { + var ranks = await context.Ranks + .ProjectTo(mapper.ConfigurationProvider) + .AsNoTracking() + .OrderByDescending(rank => rank.Name) + .ToListAsync(cancellationToken); + + return Result.Success(ranks); + } +} \ No newline at end of file diff --git a/Application/Repositories/UserRepository.cs b/Application/Repositories/UserRepository.cs new file mode 100644 index 0000000..70510ba --- /dev/null +++ b/Application/Repositories/UserRepository.cs @@ -0,0 +1,143 @@ +using Application.Classes; +using Application.DataTransferObjects.User; +using Application.Errors; +using Application.Interfaces; +using Database; +using Database.Entities; +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace Application.Repositories; + +public class UserRepository(ApplicationContext context, ILogger logger, UserManager userManager) : IUserRepository +{ + public async Task>> GetAllianceUsersAsync(Guid allianceId, CancellationToken cancellationToken) + { + var allianceUsers = await context.Users + .Where(user => user.AllianceId == allianceId) + .AsNoTracking() + .ToListAsync(cancellationToken); + + if (allianceUsers.Count <= 0) return Result.Success(new List()); + { + var users = new List(); + foreach (var user in allianceUsers) + { + users.Add(new UserDto + { + Id = user.Id, + PlayerName = user.PlayerName, + Email = user.Email!, + AllianceId = user.AllianceId, + Role = (await userManager.GetRolesAsync(user)).FirstOrDefault()! + }); + } + + return Result.Success(users); + } + + } + + public async Task> GetUserAsync(Guid userId, CancellationToken cancellationToken) + { + var userById = await context.Users + .AsNoTracking() + .FirstOrDefaultAsync(user => user.Id == userId, cancellationToken); + + if (userById is null) return Result.Failure(UserErrors.NotFound); + + var userDto = new UserDto() + { + Id = userById.Id, + Email = userById.Email!, + PlayerName = userById.PlayerName, + AllianceId = userById.AllianceId, + Role = (await userManager.GetRolesAsync(userById)).FirstOrDefault()! + }; + + return Result.Success(userDto); + } + + public async Task ChangeUserPasswordAsync(ChangePasswordDto changePasswordDto, CancellationToken cancellationToken) + { + var userToChange = await userManager.FindByIdAsync(changePasswordDto.UserId.ToString()); + + if (userToChange is null) return Result.Failure(UserErrors.NotFound); + + var checkCurrentPassword = + await userManager.CheckPasswordAsync(userToChange, changePasswordDto.CurrentPassword); + + if (!checkCurrentPassword) return Result.Failure(UserErrors.CurrentPasswordNotMatch); + + var changePasswordResult = await userManager.ChangePasswordAsync(userToChange, + changePasswordDto.CurrentPassword, changePasswordDto.NewPassword); + + return changePasswordResult.Succeeded + ? Result.Success() + : Result.Failure(UserErrors.ChangePasswordFailed); + } + + public async Task> UpdateUserAsync(UpdateUserDto updateUserDto, CancellationToken cancellationToken) + { + var userToUpdate = await userManager.FindByIdAsync(updateUserDto.Id.ToString()); + + if (userToUpdate is null) return Result.Failure(UserErrors.NotFound); + + var userRole = (await userManager.GetRolesAsync(userToUpdate)).FirstOrDefault()!; + + if (userRole != updateUserDto.Role) + { + await userManager.RemoveFromRoleAsync(userToUpdate, userRole); + var addRoleResult = await userManager.AddToRoleAsync(userToUpdate, updateUserDto.Role); + + if (!addRoleResult.Succeeded) return Result.Failure(GeneralErrors.DatabaseError); + } + + try + { + userToUpdate.PlayerName = updateUserDto.PlayerName; + var updateResult = await userManager.UpdateAsync(userToUpdate); + + return updateResult.Succeeded + ? Result.Success(new UserDto() + { + Email = userToUpdate.Email!, + PlayerName = updateUserDto.PlayerName, + Role = updateUserDto.Role, + AllianceId = userToUpdate.AllianceId, + Id = userToUpdate.Id + }) + : Result.Failure(GeneralErrors.DatabaseError); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return Result.Failure(GeneralErrors.DatabaseError); + } + + } + + public async Task DeleteUserAsync(Guid userId, CancellationToken cancellationToken) + { + var userToDelete = await context.Users + .AsNoTracking() + .FirstOrDefaultAsync(user => user.Id == userId, cancellationToken); + + if (userToDelete is null) return Result.Failure(UserErrors.NotFound); + + try + { + var deleteResult = await userManager.DeleteAsync(userToDelete); + + return deleteResult.Succeeded ? Result.Success() : Result.Failure(GeneralErrors.DatabaseError); + } + catch (Exception e) + { + + logger.LogError(e, e.Message); + return Result.Failure(GeneralErrors.DatabaseError); + } + + } +} \ No newline at end of file diff --git a/Application/Repositories/VsDuelParticipantRepository.cs b/Application/Repositories/VsDuelParticipantRepository.cs new file mode 100644 index 0000000..f170a3e --- /dev/null +++ b/Application/Repositories/VsDuelParticipantRepository.cs @@ -0,0 +1,34 @@ +using Application.Classes; +using Application.DataTransferObjects.VsDuelParticipant; +using Application.Errors; +using Application.Interfaces; +using AutoMapper; +using Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace Application.Repositories; + +public class VsDuelParticipantRepository(ApplicationContext context, IMapper mapper, ILogger logger) : IVsDuelParticipantRepository +{ + public async Task> UpdateVsDuelParticipant(VsDuelParticipantDto vsDuelParticipantDto, CancellationToken cancellationToken) + { + var participantToUpdate = await context.VsDuelParticipants + .FirstOrDefaultAsync(vsDuelParticipant => vsDuelParticipant.Id == vsDuelParticipantDto.Id, + cancellationToken); + if (participantToUpdate is null) return Result.Failure(VsDuelParticipantErrors.NotFound); + + mapper.Map(vsDuelParticipantDto, participantToUpdate); + + try + { + await context.SaveChangesAsync(cancellationToken); + return Result.Success(mapper.Map(participantToUpdate)); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return Result.Failure(GeneralErrors.DatabaseError); + } + } +} \ No newline at end of file diff --git a/Application/Repositories/VsDuelRepository.cs b/Application/Repositories/VsDuelRepository.cs index 7246bb0..1ff4ed5 100644 --- a/Application/Repositories/VsDuelRepository.cs +++ b/Application/Repositories/VsDuelRepository.cs @@ -25,27 +25,47 @@ public class VsDuelRepository(ApplicationContext context, IMapper mapper, ILogge : Result.Success(vsDuelById); } - public async Task>> GetPlayerVsDuelsAsync(Guid playerId, CancellationToken cancellationToken) + public async Task> GetVsDuelDetailAsync(Guid vsDuelId, CancellationToken cancellationToken) { - var playerVsDuels = await context.VsDuels - .Where(vsDuel => vsDuel.PlayerId == playerId) + var vsDuelDetail = await context.VsDuels + .ProjectTo(mapper.ConfigurationProvider) + .AsNoTracking() + .FirstOrDefaultAsync(vsDuel => vsDuel.Id == vsDuelId, cancellationToken); + + return vsDuelDetail is null + ? Result.Failure(VsDuelErrors.NotFound) + : Result.Success(vsDuelDetail); + } + + public async Task>> GetAllianceVsDuelsAsync(Guid allianceId, int take, CancellationToken cancellationToken) + { + var allianceVsDuels = await context.VsDuels + .Where(vsDuel => vsDuel.AllianceId == allianceId) .ProjectTo(mapper.ConfigurationProvider) + .OrderByDescending(vsDuel => vsDuel.EventDate) + .Take(take) .AsNoTracking() .ToListAsync(cancellationToken); - return Result.Success(playerVsDuels); + return Result.Success(allianceVsDuels); } - public async Task> CreateVsDuelAsync(CreateVsDuelDto createVsDuelDto, CancellationToken cancellationToken) + public async Task> CreateVsDuelAsync(CreateVsDuelDto createVsDuelDto, string createdBy, CancellationToken cancellationToken) { var newVsDuel = mapper.Map(createVsDuelDto); + newVsDuel.CreatedBy = createdBy; await context.VsDuels.AddAsync(newVsDuel, cancellationToken); try { await context.SaveChangesAsync(cancellationToken); - return Result.Success(mapper.Map(newVsDuel)); + var vsDuelParticipantsResult = + await InsertPlayersAsync(newVsDuel.Id, newVsDuel.AllianceId, cancellationToken); + + return vsDuelParticipantsResult.IsFailure + ? Result.Failure(vsDuelParticipantsResult.Error) + : Result.Success(mapper.Map(newVsDuel)); } catch (Exception e) { @@ -54,7 +74,7 @@ public class VsDuelRepository(ApplicationContext context, IMapper mapper, ILogge } } - public async Task> UpdateVsDuelAsync(UpdateVsDuelDto updateVsDuelDto, CancellationToken cancellationToken) + public async Task> UpdateVsDuelAsync(UpdateVsDuelDto updateVsDuelDto, string modifiedBy, CancellationToken cancellationToken) { var vsDuelToUpdate = await context.VsDuels .FirstOrDefaultAsync(vsDuel => vsDuel.Id == updateVsDuelDto.Id, cancellationToken); @@ -62,6 +82,7 @@ public class VsDuelRepository(ApplicationContext context, IMapper mapper, ILogge if (vsDuelToUpdate is null) return Result.Failure(VsDuelErrors.NotFound); mapper.Map(updateVsDuelDto, vsDuelToUpdate); + vsDuelToUpdate.ModifiedBy = modifiedBy; try { @@ -95,4 +116,26 @@ public class VsDuelRepository(ApplicationContext context, IMapper mapper, ILogge return Result.Failure(GeneralErrors.DatabaseError); } } + + private async Task InsertPlayersAsync(Guid vsDuelId, Guid allianceId, CancellationToken cancellationToken) + { + var alliancePlayers = await context.Players + .Where(player => player.AllianceId == allianceId) + .AsNoTracking() + .ToListAsync(cancellationToken); + + var vsDuelParticipants = alliancePlayers.Select(player => new VsDuelParticipant() { Id = Guid.CreateVersion7(), PlayerId = player.Id, VsDuelId = vsDuelId, WeeklyPoints = 0 }).ToList(); + + try + { + await context.VsDuelParticipants.AddRangeAsync(vsDuelParticipants, cancellationToken); + await context.SaveChangesAsync(cancellationToken); + return Result.Success(true); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return Result.Failure(GeneralErrors.DatabaseError); + } + } } \ No newline at end of file diff --git a/Application/Services/ClaimTypeService.cs b/Application/Services/ClaimTypeService.cs new file mode 100644 index 0000000..2b0e3bf --- /dev/null +++ b/Application/Services/ClaimTypeService.cs @@ -0,0 +1,14 @@ +using System.Security.Claims; +using Application.Interfaces; + +namespace Application.Services; + +public class ClaimTypeService : IClaimTypeService +{ + public string GetFullName(ClaimsPrincipal claimsPrincipal) + { + var userName = claimsPrincipal.FindFirstValue("playerName"); + + return string.IsNullOrEmpty(userName) ? "Unknown" : userName; + } +} \ No newline at end of file diff --git a/Application/Services/JwtService.cs b/Application/Services/JwtService.cs index 1dc24de..1bfa6a6 100644 --- a/Application/Services/JwtService.cs +++ b/Application/Services/JwtService.cs @@ -2,14 +2,16 @@ using System.Security.Claims; using System.Text; using Application.Interfaces; +using Database; using Database.Entities; using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.IdentityModel.Tokens; namespace Application.Services; -public class JwtService(IConfiguration configuration, UserManager userManager) : IJwtService +public class JwtService(IConfiguration configuration, UserManager userManager, ApplicationContext context) : IJwtService { private readonly IConfigurationSection _jwtSection = configuration.GetSection("Jwt"); public SigningCredentials GetSigningCredentials() @@ -27,11 +29,13 @@ public class JwtService(IConfiguration configuration, UserManager userMana new("email", user.Email!), new("playerName", user.PlayerName), new("userId", user.Id.ToString()), - new("allianceId", user.AllianceId.ToString()) + new("allianceId", user.AllianceId.ToString()), }; var roles = await userManager.GetRolesAsync(user); claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role))); + var userAlliance = await context.Alliances.FirstOrDefaultAsync(alliance => alliance.Id == user.AllianceId); + claims.Add(new("allianceName", userAlliance!.Name)); return claims; } diff --git a/Database/ApplicationContext.cs b/Database/ApplicationContext.cs index 5cc4ec9..3a03ff6 100644 --- a/Database/ApplicationContext.cs +++ b/Database/ApplicationContext.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; namespace Database; @@ -25,6 +26,23 @@ public class ApplicationContext(DbContextOptions options) : public DbSet VsDuels { get; set; } + public DbSet CustomEvents { get; set; } + + public DbSet MarshalGuardParticipants { get; set; } + + public DbSet VsDuelParticipants { get; set; } + + public DbSet CustomEventParticipants { get; set; } + + public DbSet DesertStormParticipants { get; set; } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + base.OnConfiguring(optionsBuilder); + optionsBuilder.ConfigureWarnings(warnings => warnings.Ignore(RelationalEventId.PendingModelChangesWarning)); + + } + protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); diff --git a/Database/Configurations/AdmonitionConfiguration.cs b/Database/Configurations/AdmonitionConfiguration.cs index 85aa39f..70118e7 100644 --- a/Database/Configurations/AdmonitionConfiguration.cs +++ b/Database/Configurations/AdmonitionConfiguration.cs @@ -9,7 +9,12 @@ public class AdmonitionConfiguration : IEntityTypeConfiguration public void Configure(EntityTypeBuilder builder) { builder.HasKey(admonition => admonition.Id); + builder.Property(admonition => admonition.Id).ValueGeneratedNever(); builder.Property(admonition => admonition.Reason).IsRequired().HasMaxLength(250); + builder.Property(admonition => admonition.CreatedOn).IsRequired(); + builder.Property(admonition => admonition.CreatedBy).IsRequired().HasMaxLength(150); + builder.Property(admonition => admonition.ModifiedOn).IsRequired(false); + builder.Property(admonition => admonition.ModifiedBy).IsRequired(false).HasMaxLength(150); } } \ No newline at end of file diff --git a/Database/Configurations/AllianceConfiguration.cs b/Database/Configurations/AllianceConfiguration.cs index 11a20e2..d0ebc93 100644 --- a/Database/Configurations/AllianceConfiguration.cs +++ b/Database/Configurations/AllianceConfiguration.cs @@ -9,9 +9,13 @@ public class AllianceConfiguration : IEntityTypeConfiguration public void Configure(EntityTypeBuilder builder) { builder.HasKey(alliance => alliance.Id); + builder.Property(alliance => alliance.Id).ValueGeneratedNever(); builder.Property(alliance => alliance.Name).IsRequired().HasMaxLength(200); builder.Property(alliance => alliance.Abbreviation).IsRequired().HasMaxLength(5); builder.Property(alliance => alliance.Server).IsRequired(); + builder.Property(alliance => alliance.CreatedOn).IsRequired(); + builder.Property(alliance => alliance.ModifiedOn).IsRequired(false); + builder.Property(alliance => alliance.ModifiedBy).IsRequired(false).HasMaxLength(150); } } \ No newline at end of file diff --git a/Database/Configurations/CustomEventConfiguration.cs b/Database/Configurations/CustomEventConfiguration.cs new file mode 100644 index 0000000..a84ac07 --- /dev/null +++ b/Database/Configurations/CustomEventConfiguration.cs @@ -0,0 +1,28 @@ +using Database.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Database.Configurations; + +public class CustomEventConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(customEvent => customEvent.Id); + builder.Property(customEnvent => customEnvent.Id).ValueGeneratedNever(); + + builder.Property(customEvent => customEvent.Name).IsRequired().HasMaxLength(150); + builder.Property(customEvent => customEvent.Description).IsRequired().HasMaxLength(500); + builder.Property(customEvent => customEvent.EventDate).IsRequired(); + builder.Property(customEvent => customEvent.IsParticipationEvent).IsRequired(); + builder.Property(customEvent => customEvent.IsPointsEvent).IsRequired(); + builder.Property(customEvent => customEvent.CreatedBy).IsRequired().HasMaxLength(150); + builder.Property(customEvent => customEvent.ModifiedBy).IsRequired(false).HasMaxLength(150); + builder.Property(customEvent => customEvent.ModifiedOn).IsRequired(false); + + builder.HasOne(c => c.Alliance) + .WithMany(a => a.CustomEvents) + .HasForeignKey(c => c.AllianceId) + .OnDelete(DeleteBehavior.Cascade); + } +} \ No newline at end of file diff --git a/Database/Configurations/CustomEventParticipantConfiguration.cs b/Database/Configurations/CustomEventParticipantConfiguration.cs new file mode 100644 index 0000000..23d2fbb --- /dev/null +++ b/Database/Configurations/CustomEventParticipantConfiguration.cs @@ -0,0 +1,29 @@ +using Database.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Database.Configurations; + +public class CustomEventParticipantConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(customEventParticipant => customEventParticipant.Id); + builder.Property(customEventParticipant => customEventParticipant.Id).ValueGeneratedNever(); + + builder.Property(customEventParticipant => customEventParticipant.PlayerId).IsRequired(); + builder.Property(customEventParticipant => customEventParticipant.CustomEventId).IsRequired(); + builder.Property(customEventParticipant => customEventParticipant.Participated).IsRequired(false); + builder.Property(customEventParticipant => customEventParticipant.AchievedPoints).IsRequired(false); + + builder.HasOne(customEventParticipant => customEventParticipant.Player) + .WithMany(player => player.CustomEventParticipants) + .HasForeignKey(customEventParticipant => customEventParticipant.PlayerId) + .OnDelete(DeleteBehavior.Restrict); + + builder.HasOne(customEventParticipant => customEventParticipant.CustomEvent) + .WithMany(customEvent => customEvent.CustomEventParticipants) + .HasForeignKey(customEventParticipant => customEventParticipant.CustomEventId) + .OnDelete(DeleteBehavior.Cascade); + } +} \ No newline at end of file diff --git a/Database/Configurations/DesertStormConfiguration.cs b/Database/Configurations/DesertStormConfiguration.cs index 1d5337f..b88f7df 100644 --- a/Database/Configurations/DesertStormConfiguration.cs +++ b/Database/Configurations/DesertStormConfiguration.cs @@ -9,10 +9,20 @@ public class DesertStormConfiguration : IEntityTypeConfiguration public void Configure(EntityTypeBuilder builder) { builder.HasKey(desertStorm => desertStorm.Id); + builder.Property(desertStorm => desertStorm.Id).ValueGeneratedNever(); - builder.Property(desertStorm => desertStorm.CalendarWeek).IsRequired(); - builder.Property(desertStorm => desertStorm.Participated).IsRequired(); - builder.Property(desertStorm => desertStorm.Registered).IsRequired(); - builder.Property(desertStorm => desertStorm.Year).IsRequired(); + builder.Property(desertStorm => desertStorm.EventDate).IsRequired(); + builder.Property(desertStorm => desertStorm.OpponentServer).IsRequired(); + builder.Property(desertStorm => desertStorm.Won).IsRequired(); + builder.Property(desertStorm => desertStorm.OpposingParticipants).IsRequired(); + builder.Property(desertStorm => desertStorm.CreatedBy).IsRequired().HasMaxLength(150); + builder.Property(desertStorm => desertStorm.OpponentName).IsRequired().HasMaxLength(150); + builder.Property(desertStorm => desertStorm.ModifiedBy).IsRequired(false).HasMaxLength(150); + builder.Property(desertStorm => desertStorm.ModifiedOn).IsRequired(false); + + builder.HasOne(desertStorm => desertStorm.Alliance) + .WithMany(alliance => alliance.DesertStorms) + .HasForeignKey(desertStorm => desertStorm.AllianceId) + .OnDelete(DeleteBehavior.Cascade); } } \ No newline at end of file diff --git a/Database/Configurations/DesertStormParticipantConfiguration.cs b/Database/Configurations/DesertStormParticipantConfiguration.cs new file mode 100644 index 0000000..2e519ca --- /dev/null +++ b/Database/Configurations/DesertStormParticipantConfiguration.cs @@ -0,0 +1,30 @@ +using Database.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Database.Configurations; + +public class DesertStormParticipantConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(desertStormParticipant => desertStormParticipant.Id); + builder.Property(desertStormParticipant => desertStormParticipant.Id).ValueGeneratedNever(); + + builder.Property(desertStormParticipant => desertStormParticipant.PlayerId).IsRequired(); + builder.Property(desertStormParticipant => desertStormParticipant.DesertStormId).IsRequired(); + builder.Property(desertStormParticipant => desertStormParticipant.Participated).IsRequired(); + builder.Property(desertStormParticipant => desertStormParticipant.Registered).IsRequired(); + builder.Property(desertStormParticipant => desertStormParticipant.StartPlayer).IsRequired(); + + builder.HasOne(desertStormParticipant => desertStormParticipant.DesertStorm) + .WithMany(desertStorm => desertStorm.DesertStormParticipants) + .HasForeignKey(desertStormParticipant => desertStormParticipant.DesertStormId) + .OnDelete(DeleteBehavior.Cascade); + + builder.HasOne(desertStormParticipant => desertStormParticipant.Player) + .WithMany(player => player.DesertStormParticipants) + .HasForeignKey(desertStormParticipant => desertStormParticipant.PlayerId) + .OnDelete(DeleteBehavior.Restrict); + } +} \ No newline at end of file diff --git a/Database/Configurations/MarshalGuardConfiguration.cs b/Database/Configurations/MarshalGuardConfiguration.cs index 967bb68..23578a0 100644 --- a/Database/Configurations/MarshalGuardConfiguration.cs +++ b/Database/Configurations/MarshalGuardConfiguration.cs @@ -9,10 +9,19 @@ public class MarshalGuardConfiguration : IEntityTypeConfiguration public void Configure(EntityTypeBuilder builder) { builder.HasKey(marshalGuard => marshalGuard.Id); + builder.Property(marshalGuard => marshalGuard.Id).ValueGeneratedNever(); - builder.Property(marshalGuard => marshalGuard.Year).IsRequired(); - builder.Property(marshalGuard => marshalGuard.Day).IsRequired(); - builder.Property(marshalGuard => marshalGuard.Month).IsRequired(); - builder.Property(marshalGuard => marshalGuard.Participated).IsRequired(); + builder.Property(marshalGuard => marshalGuard.EventDate).IsRequired(); + builder.Property(marshalGuard => marshalGuard.Level).IsRequired(); + builder.Property(marshalGuard => marshalGuard.RewardPhase).IsRequired(); + builder.Property(marshalGuard => marshalGuard.AllianceSize).IsRequired(); + builder.Property(marshalGuard => marshalGuard.CreatedBy).IsRequired().HasMaxLength(150); + builder.Property(marshalGuard => marshalGuard.ModifiedOn).IsRequired(false); + builder.Property(marshalGuard => marshalGuard.ModifiedBy).IsRequired(false).HasMaxLength(150); + + builder.HasOne(marshalGuard => marshalGuard.Alliance) + .WithMany(alliance => alliance.MarshalGuards) + .HasForeignKey(marshalGuard => marshalGuard.AllianceId) + .OnDelete(DeleteBehavior.Cascade); } } \ No newline at end of file diff --git a/Database/Configurations/MarshalGuardParticipantConfiguration.cs b/Database/Configurations/MarshalGuardParticipantConfiguration.cs new file mode 100644 index 0000000..f675d0e --- /dev/null +++ b/Database/Configurations/MarshalGuardParticipantConfiguration.cs @@ -0,0 +1,28 @@ +using Database.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Database.Configurations; + +public class MarshalGuardParticipantConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(marshalGuardParticipant => marshalGuardParticipant.Id); + builder.Property(marshalGuardParticipant => marshalGuardParticipant.Id).ValueGeneratedNever(); + + builder.Property(marshalGuardParticipant => marshalGuardParticipant.Participated).IsRequired(); + builder.Property(marshalGuardParticipant => marshalGuardParticipant.PlayerId).IsRequired(); + builder.Property(marshalGuardParticipant => marshalGuardParticipant.MarshalGuardId).IsRequired(); + + builder.HasOne(marshalGuardParticipant => marshalGuardParticipant.MarshalGuard) + .WithMany(marshalGuard => marshalGuard.MarshalGuardParticipants) + .HasForeignKey(marshalGuardParticipant => marshalGuardParticipant.MarshalGuardId) + .OnDelete(DeleteBehavior.Cascade); + + builder.HasOne(marshalGuardParticipant => marshalGuardParticipant.Player) + .WithMany(player => player.MarshalGuardParticipants) + .HasForeignKey(marshalGuardParticipant => marshalGuardParticipant.PlayerId) + .OnDelete(DeleteBehavior.Restrict); + } +} \ No newline at end of file diff --git a/Database/Configurations/NoteConfiguration.cs b/Database/Configurations/NoteConfiguration.cs index 4c0bdca..2a72da9 100644 --- a/Database/Configurations/NoteConfiguration.cs +++ b/Database/Configurations/NoteConfiguration.cs @@ -9,7 +9,12 @@ public class NoteConfiguration : IEntityTypeConfiguration public void Configure(EntityTypeBuilder builder) { builder.HasKey(note => note.Id); + builder.Property(note => note.Id).ValueGeneratedNever(); builder.Property(note => note.PlayerNote).IsRequired().HasMaxLength(500); + builder.Property(note => note.CreatedOn).IsRequired(); + builder.Property(note => note.CreatedBy).IsRequired().HasMaxLength(150); + builder.Property(note => note.ModifiedOn).IsRequired(false); + builder.Property(note => note.ModifiedBy).IsRequired(false).HasMaxLength(150); } } \ No newline at end of file diff --git a/Database/Configurations/PlayerConfiguration.cs b/Database/Configurations/PlayerConfiguration.cs index ba9f072..c341d29 100644 --- a/Database/Configurations/PlayerConfiguration.cs +++ b/Database/Configurations/PlayerConfiguration.cs @@ -9,30 +9,25 @@ public class PlayerConfiguration : IEntityTypeConfiguration public void Configure(EntityTypeBuilder builder) { builder.HasKey(player => player.Id); + builder.Property(player => player.Id).ValueGeneratedNever(); builder.Property(player => player.PlayerName).IsRequired().HasMaxLength(250); - builder.Property(player => player.Level).IsRequired().HasMaxLength(3); + builder.Property(player => player.Level).IsRequired(); + builder.Property(player => player.CreatedOn).IsRequired(); + builder.Property(player => player.CreatedBy).IsRequired().HasMaxLength(150); + builder.Property(player => player.ModifiedOn).IsRequired(false); + builder.Property(player => player.ModifiedBy).IsRequired(false).HasMaxLength(150); + + builder.HasOne(player => player.Alliance) + .WithMany(alliance => alliance.Players) + .HasForeignKey(player => player.AllianceId) + .OnDelete(DeleteBehavior.Cascade); builder.HasOne(player => player.Rank) .WithMany(rank => rank.Players) .HasForeignKey(player => player.RankId) .OnDelete(DeleteBehavior.Cascade); - builder.HasMany(player => player.VsDuels) - .WithOne(vsDuel => vsDuel.Player) - .HasForeignKey(vsDuel => vsDuel.PlayerId) - .OnDelete(DeleteBehavior.Cascade); - - builder.HasMany(player => player.DesertStorms) - .WithOne(desertStorm => desertStorm.Player) - .HasForeignKey(desertStorm => desertStorm.PlayerId) - .OnDelete(DeleteBehavior.Cascade); - - builder.HasMany(player => player.MarshalGuards) - .WithOne(marshalGuard => marshalGuard.Player) - .HasForeignKey(marshalGuard => marshalGuard.PlayerId) - .OnDelete(DeleteBehavior.Cascade); - builder.HasMany(player => player.Notes) .WithOne(notes => notes.Player) .HasForeignKey(note => note.PlayerId) diff --git a/Database/Configurations/VsDuelConfiguration.cs b/Database/Configurations/VsDuelConfiguration.cs index fddd609..fd11c79 100644 --- a/Database/Configurations/VsDuelConfiguration.cs +++ b/Database/Configurations/VsDuelConfiguration.cs @@ -9,9 +9,21 @@ public class VsDuelConfiguration : IEntityTypeConfiguration public void Configure(EntityTypeBuilder builder) { builder.HasKey(vsDuel => vsDuel.Id); + builder.Property(vsDuel => vsDuel.Id).ValueGeneratedNever(); - builder.Property(vsDuel => vsDuel.Year).IsRequired(); - builder.Property(vsDuel => vsDuel.WeeklyPoints).IsRequired(); - builder.Property(vsDuel => vsDuel.CalendarWeek).IsRequired(); + builder.Property(vsDuel => vsDuel.EventDate).IsRequired(); + builder.Property(vsDuel => vsDuel.Won).IsRequired(); + builder.Property(vsDuel => vsDuel.OpponentName).IsRequired().HasMaxLength(150); + builder.Property(vsDuel => vsDuel.OpponentServer).IsRequired(); + builder.Property(vsDuel => vsDuel.OpponentPower).IsRequired(); + builder.Property(vsDuel => vsDuel.OpponentSize).IsRequired(); + builder.Property(vsDuel => vsDuel.CreatedBy).IsRequired().HasMaxLength(150); + builder.Property(vsDuel => vsDuel.ModifiedOn).IsRequired(false); + builder.Property(vsDuel => vsDuel.ModifiedBy).IsRequired(false).HasMaxLength(150); + + builder.HasOne(vsDuel => vsDuel.Alliance) + .WithMany(alliance => alliance.VsDuels) + .HasForeignKey(vsDuel => vsDuel.AllianceId) + .OnDelete(DeleteBehavior.Cascade); } } \ No newline at end of file diff --git a/Database/Configurations/VsDuelParticipantConfiguration.cs b/Database/Configurations/VsDuelParticipantConfiguration.cs new file mode 100644 index 0000000..96cf8bd --- /dev/null +++ b/Database/Configurations/VsDuelParticipantConfiguration.cs @@ -0,0 +1,28 @@ +using Database.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Database.Configurations; + +public class VsDuelParticipantConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(vsDuelParticipant => vsDuelParticipant.Id); + builder.Property(vsDuelParticipant => vsDuelParticipant.Id).ValueGeneratedNever(); + + builder.Property(vsDuelParticipant => vsDuelParticipant.PlayerId).IsRequired(); + builder.Property(vsDuelParticipant => vsDuelParticipant.VsDuelId).IsRequired(); + builder.Property(vsDuelParticipant => vsDuelParticipant.WeeklyPoints).IsRequired(); + + builder.HasOne(vsDuelParticipant => vsDuelParticipant.Player) + .WithMany(player => player.VsDuelParticipants) + .HasForeignKey(vsDuelParticipant => vsDuelParticipant.PlayerId) + .OnDelete(DeleteBehavior.Restrict); + + builder.HasOne(vsDuelParticipant => vsDuelParticipant.VsDuel) + .WithMany(vsDuel => vsDuel.VsDuelParticipants) + .HasForeignKey(vsDuelParticipant => vsDuelParticipant.VsDuelId) + .OnDelete(DeleteBehavior.Cascade); + } +} \ No newline at end of file diff --git a/Database/Database.csproj b/Database/Database.csproj index a688b5a..3efb6d8 100644 --- a/Database/Database.csproj +++ b/Database/Database.csproj @@ -1,18 +1,18 @@  - net8.0 + net9.0 enable enable - + - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Database/DatabaseDependencyInjection.cs b/Database/DatabaseDependencyInjection.cs index 1389d12..9f4c0a5 100644 --- a/Database/DatabaseDependencyInjection.cs +++ b/Database/DatabaseDependencyInjection.cs @@ -1,5 +1,4 @@ -using System.Reflection; -using Database.Entities; +using Database.Entities; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; diff --git a/Database/Entities/Admonition.cs b/Database/Entities/Admonition.cs index 44d2636..0d990e7 100644 --- a/Database/Entities/Admonition.cs +++ b/Database/Entities/Admonition.cs @@ -4,7 +4,15 @@ public class Admonition : BaseEntity { public required string Reason { get; set; } + public DateTime CreatedOn { get; set; } + + public required string CreatedBy { get; set; } + + public DateTime? ModifiedOn { get; set; } + + public string? ModifiedBy { get; set; } + public Guid PlayerId { get; set; } - public required Player Player { get; set; } + public Player Player { get; set; } = null!; } \ No newline at end of file diff --git a/Database/Entities/Alliance.cs b/Database/Entities/Alliance.cs index fae0ed4..8bf66f7 100644 --- a/Database/Entities/Alliance.cs +++ b/Database/Entities/Alliance.cs @@ -8,7 +8,21 @@ public class Alliance : BaseEntity public required string Abbreviation { get; set; } + public DateTime CreatedOn { get; set; } + + public DateTime? ModifiedOn { get; set; } + + public string? ModifiedBy { get; set; } + public ICollection Players { get; set; } = []; public ICollection Users { get; set; } = []; + + public ICollection DesertStorms { get; set; } = []; + + public ICollection CustomEvents { get; set; } = []; + + public ICollection MarshalGuards { get; set; } = []; + + public ICollection VsDuels { get; set; } = []; } \ No newline at end of file diff --git a/Database/Entities/CustomEvent.cs b/Database/Entities/CustomEvent.cs new file mode 100644 index 0000000..fb62db0 --- /dev/null +++ b/Database/Entities/CustomEvent.cs @@ -0,0 +1,26 @@ +namespace Database.Entities; + +public class CustomEvent : BaseEntity +{ + public Guid AllianceId { get; set; } + + public Alliance Alliance { get; set; } = null!; + + public required string Name { get; set; } + + public required string Description { get; set; } + + public bool IsPointsEvent { get; set; } + + public bool IsParticipationEvent { get; set; } + + public DateTime EventDate { get; set; } + + public required string CreatedBy { get; set; } + + public DateTime? ModifiedOn { get; set; } + + public string? ModifiedBy { get; set; } + + public ICollection CustomEventParticipants { get; set; } = []; +} \ No newline at end of file diff --git a/Database/Entities/CustomEventParticipant.cs b/Database/Entities/CustomEventParticipant.cs new file mode 100644 index 0000000..936e133 --- /dev/null +++ b/Database/Entities/CustomEventParticipant.cs @@ -0,0 +1,16 @@ +namespace Database.Entities; + +public class CustomEventParticipant : BaseEntity +{ + public Player Player { get; set; } = null!; + + public Guid PlayerId { get; set; } + + public CustomEvent CustomEvent { get; set; } = null!; + + public Guid CustomEventId { get; set; } + + public bool? Participated { get; set; } + + public long? AchievedPoints { get; set; } +} \ No newline at end of file diff --git a/Database/Entities/DesertStorm.cs b/Database/Entities/DesertStorm.cs index 1e6ca88..17cfdd3 100644 --- a/Database/Entities/DesertStorm.cs +++ b/Database/Entities/DesertStorm.cs @@ -2,16 +2,25 @@ public class DesertStorm : BaseEntity { - public bool Registered { get; set; } + public Guid AllianceId { get; set; } - public bool Participated { get; set; } + public Alliance Alliance { get; set; } = null!; - public int Year { get; set; } + public DateTime EventDate { get; set; } - public int CalendarWeek { get; set; } + public required string CreatedBy { get; set; } + public DateTime? ModifiedOn { get; set; } - public Guid PlayerId { get; set; } + public string? ModifiedBy { get; set; } - public required Player Player { get; set; } + public bool Won { get; set; } + + public required string OpponentName { get; set; } + + public int OpponentServer { get; set; } + + public int OpposingParticipants { get; set; } + + public ICollection DesertStormParticipants { get; set; } = []; } \ No newline at end of file diff --git a/Database/Entities/DesertStormParticipant.cs b/Database/Entities/DesertStormParticipant.cs new file mode 100644 index 0000000..aa04b38 --- /dev/null +++ b/Database/Entities/DesertStormParticipant.cs @@ -0,0 +1,18 @@ +namespace Database.Entities; + +public class DesertStormParticipant : BaseEntity +{ + public Player Player { get; set; } = null!; + + public Guid PlayerId { get; set; } + + public DesertStorm DesertStorm { get; set; } = null!; + + public Guid DesertStormId { get; set; } + + public bool Registered { get; set; } + + public bool Participated { get; set; } + + public bool StartPlayer { get; set; } +} \ No newline at end of file diff --git a/Database/Entities/MarshalGuard.cs b/Database/Entities/MarshalGuard.cs index 9cec772..c291c69 100644 --- a/Database/Entities/MarshalGuard.cs +++ b/Database/Entities/MarshalGuard.cs @@ -2,15 +2,23 @@ public class MarshalGuard : BaseEntity { - public bool Participated { get; set; } + public Guid AllianceId { get; set; } - public int Year { get; set; } + public Alliance Alliance { get; set; } = null!; - public int Month { get; set; } + public DateTime EventDate { get; set; } - public int Day { get; set; } + public required string CreatedBy { get; set; } - public Guid PlayerId { get; set; } + public DateTime? ModifiedOn { get; set; } - public required Player Player { get; set; } + public string? ModifiedBy { get; set; } + + public int Level { get; set; } + + public int RewardPhase { get; set; } + + public int AllianceSize { get; set; } + + public ICollection MarshalGuardParticipants { get; set; } = []; } \ No newline at end of file diff --git a/Database/Entities/MarshalGuardParticipant.cs b/Database/Entities/MarshalGuardParticipant.cs new file mode 100644 index 0000000..52b4545 --- /dev/null +++ b/Database/Entities/MarshalGuardParticipant.cs @@ -0,0 +1,15 @@ +namespace Database.Entities; + +public class MarshalGuardParticipant : BaseEntity +{ + public Player Player { get; set; } = null!; + + public Guid PlayerId { get; set; } + + public MarshalGuard MarshalGuard { get; set; } = null!; + + public Guid MarshalGuardId { get; set; } + + public bool Participated { get; set; } + +} \ No newline at end of file diff --git a/Database/Entities/Note.cs b/Database/Entities/Note.cs index b9ae908..dd6ffd6 100644 --- a/Database/Entities/Note.cs +++ b/Database/Entities/Note.cs @@ -4,7 +4,15 @@ public class Note : BaseEntity { public required string PlayerNote { get; set; } + public DateTime CreatedOn{ get; set; } + + public required string CreatedBy { get; set; } + + public DateTime? ModifiedOn { get; set; } + + public string? ModifiedBy { get; set; } + public Guid PlayerId { get; set; } - public required Player Player { get; set; } + public Player Player { get; set; } = null!; } \ No newline at end of file diff --git a/Database/Entities/Player.cs b/Database/Entities/Player.cs index 7f111c4..a6d4f63 100644 --- a/Database/Entities/Player.cs +++ b/Database/Entities/Player.cs @@ -4,24 +4,33 @@ public class Player : BaseEntity { public required string PlayerName { get; set; } - public required Rank Rank { get; set; } + public Rank Rank { get; set; } = null!; public Guid RankId { get; set; } - public Alliance Alliance { get; set; } + public Alliance Alliance { get; set; } = null!; public Guid AllianceId { get; set; } - public required string Level { get; set; } + public int Level { get; set; } - public ICollection DesertStorms { get; set; } = []; + public DateTime CreatedOn { get; set; } - public ICollection VsDuels { get; set; } = []; + public required string CreatedBy { get; set; } - public ICollection MarshalGuards { get; set; } = []; + public DateTime? ModifiedOn { get; set; } + + public string? ModifiedBy { get; set; } + + public ICollection DesertStormParticipants { get; set; } = []; + + public ICollection VsDuelParticipants { get; set; } = []; + + public ICollection MarshalGuardParticipants { get; set; } = []; public ICollection Admonitions { get; set; } = []; - public ICollection Notes { get; set; } = []; + + public ICollection CustomEventParticipants { get; set; } = []; } \ No newline at end of file diff --git a/Database/Entities/User.cs b/Database/Entities/User.cs index 42c3163..1ab0b9e 100644 --- a/Database/Entities/User.cs +++ b/Database/Entities/User.cs @@ -4,7 +4,7 @@ namespace Database.Entities; public class User : IdentityUser { - public required Alliance Alliance { get; set; } + public Alliance Alliance { get; set; } = null!; public Guid AllianceId { get; set; } diff --git a/Database/Entities/VsDuel.cs b/Database/Entities/VsDuel.cs index 7227fd8..ab5feac 100644 --- a/Database/Entities/VsDuel.cs +++ b/Database/Entities/VsDuel.cs @@ -2,14 +2,27 @@ public class VsDuel : BaseEntity { - public int WeeklyPoints { get; set; } + public Guid AllianceId { get; set; } - public int Year { get; set; } + public Alliance Alliance { get; set; } = null!; - public int CalendarWeek { get; set; } + public DateTime EventDate { get; set; } + public required string CreatedBy { get; set; } - public Guid PlayerId { get; set; } + public DateTime? ModifiedOn { get; set; } - public required Player Player { get; set; } + public string? ModifiedBy { get; set; } + + public bool Won { get; set; } + + public required string OpponentName { get; set; } + + public int OpponentServer { get; set; } + + public long OpponentPower { get; set; } + + public int OpponentSize { get; set; } + + public ICollection VsDuelParticipants { get; set; } = []; } \ No newline at end of file diff --git a/Database/Entities/VsDuelParticipant.cs b/Database/Entities/VsDuelParticipant.cs new file mode 100644 index 0000000..03f65af --- /dev/null +++ b/Database/Entities/VsDuelParticipant.cs @@ -0,0 +1,14 @@ +namespace Database.Entities; + +public class VsDuelParticipant : BaseEntity +{ + public Player Player { get; set; } = null!; + + public Guid PlayerId { get; set; } + + public VsDuel VsDuel { get; set; } = null!; + + public Guid VsDuelId { get; set; } + + public long WeeklyPoints { get; set; } +} \ No newline at end of file diff --git a/Database/Migrations/20241001064648_FixAllianceConfiguration.cs b/Database/Migrations/20241001064648_FixAllianceConfiguration.cs deleted file mode 100644 index c02e4e4..0000000 --- a/Database/Migrations/20241001064648_FixAllianceConfiguration.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Database.Migrations -{ - /// - public partial class FixAllianceConfiguration : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "Name", - schema: "dbo", - table: "Alliances", - type: "nvarchar(200)", - maxLength: 200, - nullable: false, - oldClrType: typeof(string), - oldType: "nvarchar(max)"); - - migrationBuilder.AlterColumn( - name: "Abbreviation", - schema: "dbo", - table: "Alliances", - type: "nvarchar(5)", - maxLength: 5, - nullable: false, - oldClrType: typeof(string), - oldType: "nvarchar(max)"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "Name", - schema: "dbo", - table: "Alliances", - type: "nvarchar(max)", - nullable: false, - oldClrType: typeof(string), - oldType: "nvarchar(200)", - oldMaxLength: 200); - - migrationBuilder.AlterColumn( - name: "Abbreviation", - schema: "dbo", - table: "Alliances", - type: "nvarchar(max)", - nullable: false, - oldClrType: typeof(string), - oldType: "nvarchar(5)", - oldMaxLength: 5); - } - } -} diff --git a/Database/Migrations/20241001064648_FixAllianceConfiguration.Designer.cs b/Database/Migrations/20241113124240_Init.Designer.cs similarity index 60% rename from Database/Migrations/20241001064648_FixAllianceConfiguration.Designer.cs rename to Database/Migrations/20241113124240_Init.Designer.cs index 0df732e..ae121eb 100644 --- a/Database/Migrations/20241001064648_FixAllianceConfiguration.Designer.cs +++ b/Database/Migrations/20241113124240_Init.Designer.cs @@ -12,8 +12,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Database.Migrations { [DbContext(typeof(ApplicationContext))] - [Migration("20241001064648_FixAllianceConfiguration")] - partial class FixAllianceConfiguration + [Migration("20241113124240_Init")] + partial class Init { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -21,7 +21,7 @@ namespace Database.Migrations #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("dbo") - .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("ProductVersion", "9.0.0") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -32,6 +32,21 @@ namespace Database.Migrations .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("ModifiedBy") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); + b.Property("PlayerId") .HasColumnType("uniqueidentifier"); @@ -58,6 +73,18 @@ namespace Database.Migrations .HasMaxLength(5) .HasColumnType("nvarchar(5)"); + b.Property("CreatedOn") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValue(new DateTime(2024, 11, 13, 13, 42, 40, 84, DateTimeKind.Local).AddTicks(3748)); + + b.Property("ModifiedBy") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); + b.Property("Name") .IsRequired() .HasMaxLength(200) @@ -71,15 +98,134 @@ namespace Database.Migrations b.ToTable("Alliances", "dbo"); }); + modelBuilder.Entity("Database.Entities.CustomEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AllianceId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("EventDate") + .HasColumnType("datetime2"); + + b.Property("IsParticipationEvent") + .HasColumnType("bit"); + + b.Property("IsPointsEvent") + .HasColumnType("bit"); + + b.Property("ModifiedBy") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.HasKey("Id"); + + b.HasIndex("AllianceId"); + + b.ToTable("CustomEvents", "dbo"); + }); + + modelBuilder.Entity("Database.Entities.CustomEventParticipant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AchievedPoints") + .HasColumnType("int"); + + b.Property("CustomEventId") + .HasColumnType("uniqueidentifier"); + + b.Property("Participated") + .HasColumnType("bit"); + + b.Property("PlayerId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("CustomEventId"); + + b.HasIndex("PlayerId"); + + b.ToTable("CustomEventParticipants", "dbo"); + }); + modelBuilder.Entity("Database.Entities.DesertStorm", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); - b.Property("CalendarWeek") + b.Property("AllianceId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("EventDate") + .HasColumnType("datetime2"); + + b.Property("ModifiedBy") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); + + b.Property("OpponentName") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("OpponentServer") .HasColumnType("int"); + b.Property("OpposingParticipants") + .HasColumnType("int"); + + b.Property("Won") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AllianceId"); + + b.ToTable("DesertStorms", "dbo"); + }); + + modelBuilder.Entity("Database.Entities.DesertStormParticipant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DesertStormId") + .HasColumnType("uniqueidentifier"); + b.Property("Participated") .HasColumnType("bit"); @@ -89,14 +235,13 @@ namespace Database.Migrations b.Property("Registered") .HasColumnType("bit"); - b.Property("Year") - .HasColumnType("int"); - b.HasKey("Id"); + b.HasIndex("DesertStormId"); + b.HasIndex("PlayerId"); - b.ToTable("DesertStorms", "dbo"); + b.ToTable("DesertStormParticipants", "dbo"); }); modelBuilder.Entity("Database.Entities.MarshalGuard", b => @@ -105,26 +250,62 @@ namespace Database.Migrations .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); - b.Property("Day") + b.Property("AllianceId") + .HasColumnType("uniqueidentifier"); + + b.Property("AllianceSize") .HasColumnType("int"); - b.Property("Month") + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("EventDate") + .HasColumnType("datetime2"); + + b.Property("Level") .HasColumnType("int"); + b.Property("ModifiedBy") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); + + b.Property("RewardPhase") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("AllianceId"); + + b.ToTable("MarshalGuards", "dbo"); + }); + + modelBuilder.Entity("Database.Entities.MarshalGuardParticipant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("MarshalGuardId") + .HasColumnType("uniqueidentifier"); + b.Property("Participated") .HasColumnType("bit"); b.Property("PlayerId") .HasColumnType("uniqueidentifier"); - b.Property("Year") - .HasColumnType("int"); - b.HasKey("Id"); + b.HasIndex("MarshalGuardId"); + b.HasIndex("PlayerId"); - b.ToTable("MarshalGuards", "dbo"); + b.ToTable("MarshalGuardParticipants", "dbo"); }); modelBuilder.Entity("Database.Entities.Note", b => @@ -133,6 +314,23 @@ namespace Database.Migrations .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("CreatedOn") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValue(new DateTime(2024, 11, 13, 13, 42, 40, 95, DateTimeKind.Local).AddTicks(4388)); + + b.Property("ModifiedBy") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); + b.Property("PlayerId") .HasColumnType("uniqueidentifier"); @@ -157,10 +355,25 @@ namespace Database.Migrations b.Property("AllianceId") .HasColumnType("uniqueidentifier"); - b.Property("Level") + b.Property("CreatedBy") .IsRequired() - .HasMaxLength(3) - .HasColumnType("nvarchar(3)"); + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("CreatedOn") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValue(new DateTime(2024, 11, 13, 13, 42, 40, 96, DateTimeKind.Local).AddTicks(5768)); + + b.Property("Level") + .HasColumnType("int"); + + b.Property("ModifiedBy") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); b.Property("PlayerName") .IsRequired() @@ -304,23 +517,70 @@ namespace Database.Migrations .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); - b.Property("CalendarWeek") + b.Property("AllianceId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("EventDate") + .HasColumnType("datetime2"); + + b.Property("ModifiedBy") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); + + b.Property("OpponentName") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("OpponentPower") + .HasColumnType("bigint"); + + b.Property("OpponentServer") .HasColumnType("int"); + b.Property("OpponentSize") + .HasColumnType("int"); + + b.Property("Won") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AllianceId"); + + b.ToTable("VsDuels", "dbo"); + }); + + modelBuilder.Entity("Database.Entities.VsDuelParticipant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + b.Property("PlayerId") .HasColumnType("uniqueidentifier"); - b.Property("WeeklyPoints") - .HasColumnType("int"); + b.Property("VsDuelId") + .HasColumnType("uniqueidentifier"); - b.Property("Year") + b.Property("WeeklyPoints") .HasColumnType("int"); b.HasKey("Id"); b.HasIndex("PlayerId"); - b.ToTable("VsDuels", "dbo"); + b.HasIndex("VsDuelId"); + + b.ToTable("VsDuelParticipants", "dbo"); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => @@ -491,25 +751,93 @@ namespace Database.Migrations b.Navigation("Player"); }); - modelBuilder.Entity("Database.Entities.DesertStorm", b => + modelBuilder.Entity("Database.Entities.CustomEvent", b => { - b.HasOne("Database.Entities.Player", "Player") - .WithMany("DesertStorms") - .HasForeignKey("PlayerId") + b.HasOne("Database.Entities.Alliance", "Alliance") + .WithMany("CustomEvents") + .HasForeignKey("AllianceId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + b.Navigation("Alliance"); + }); + + modelBuilder.Entity("Database.Entities.CustomEventParticipant", b => + { + b.HasOne("Database.Entities.CustomEvent", "CustomEvent") + .WithMany("CustomEventParticipants") + .HasForeignKey("CustomEventId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Database.Entities.Player", "Player") + .WithMany("CustomEventParticipants") + .HasForeignKey("PlayerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CustomEvent"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Database.Entities.DesertStorm", b => + { + b.HasOne("Database.Entities.Alliance", "Alliance") + .WithMany("DesertStorms") + .HasForeignKey("AllianceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Alliance"); + }); + + modelBuilder.Entity("Database.Entities.DesertStormParticipant", b => + { + b.HasOne("Database.Entities.DesertStorm", "DesertStorm") + .WithMany("DesertStormParticipants") + .HasForeignKey("DesertStormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Database.Entities.Player", "Player") + .WithMany("DesertStormParticipants") + .HasForeignKey("PlayerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("DesertStorm"); + b.Navigation("Player"); }); modelBuilder.Entity("Database.Entities.MarshalGuard", b => { - b.HasOne("Database.Entities.Player", "Player") + b.HasOne("Database.Entities.Alliance", "Alliance") .WithMany("MarshalGuards") - .HasForeignKey("PlayerId") + .HasForeignKey("AllianceId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + b.Navigation("Alliance"); + }); + + modelBuilder.Entity("Database.Entities.MarshalGuardParticipant", b => + { + b.HasOne("Database.Entities.MarshalGuard", "MarshalGuard") + .WithMany("MarshalGuardParticipants") + .HasForeignKey("MarshalGuardId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Database.Entities.Player", "Player") + .WithMany("MarshalGuardParticipants") + .HasForeignKey("PlayerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("MarshalGuard"); + b.Navigation("Player"); }); @@ -556,13 +884,32 @@ namespace Database.Migrations modelBuilder.Entity("Database.Entities.VsDuel", b => { - b.HasOne("Database.Entities.Player", "Player") + b.HasOne("Database.Entities.Alliance", "Alliance") .WithMany("VsDuels") + .HasForeignKey("AllianceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Alliance"); + }); + + modelBuilder.Entity("Database.Entities.VsDuelParticipant", b => + { + b.HasOne("Database.Entities.Player", "Player") + .WithMany("VsDuelParticipants") .HasForeignKey("PlayerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Database.Entities.VsDuel", "VsDuel") + .WithMany("VsDuelParticipants") + .HasForeignKey("VsDuelId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); b.Navigation("Player"); + + b.Navigation("VsDuel"); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => @@ -618,28 +965,58 @@ namespace Database.Migrations modelBuilder.Entity("Database.Entities.Alliance", b => { + b.Navigation("CustomEvents"); + + b.Navigation("DesertStorms"); + + b.Navigation("MarshalGuards"); + b.Navigation("Players"); b.Navigation("Users"); + + b.Navigation("VsDuels"); + }); + + modelBuilder.Entity("Database.Entities.CustomEvent", b => + { + b.Navigation("CustomEventParticipants"); + }); + + modelBuilder.Entity("Database.Entities.DesertStorm", b => + { + b.Navigation("DesertStormParticipants"); + }); + + modelBuilder.Entity("Database.Entities.MarshalGuard", b => + { + b.Navigation("MarshalGuardParticipants"); }); modelBuilder.Entity("Database.Entities.Player", b => { b.Navigation("Admonitions"); - b.Navigation("DesertStorms"); + b.Navigation("CustomEventParticipants"); - b.Navigation("MarshalGuards"); + b.Navigation("DesertStormParticipants"); + + b.Navigation("MarshalGuardParticipants"); b.Navigation("Notes"); - b.Navigation("VsDuels"); + b.Navigation("VsDuelParticipants"); }); modelBuilder.Entity("Database.Entities.Rank", b => { b.Navigation("Players"); }); + + modelBuilder.Entity("Database.Entities.VsDuel", b => + { + b.Navigation("VsDuelParticipants"); + }); #pragma warning restore 612, 618 } } diff --git a/Database/Migrations/20240930140736_Init.cs b/Database/Migrations/20241113124240_Init.cs similarity index 60% rename from Database/Migrations/20240930140736_Init.cs rename to Database/Migrations/20241113124240_Init.cs index f8f3ece..60644a7 100644 --- a/Database/Migrations/20240930140736_Init.cs +++ b/Database/Migrations/20241113124240_Init.cs @@ -23,8 +23,11 @@ namespace Database.Migrations { Id = table.Column(type: "uniqueidentifier", nullable: false), Server = table.Column(type: "int", nullable: false), - Name = table.Column(type: "nvarchar(max)", nullable: false), - Abbreviation = table.Column(type: "nvarchar(max)", nullable: false) + Name = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + Abbreviation = table.Column(type: "nvarchar(5)", maxLength: 5, nullable: false), + CreatedOn = table.Column(type: "datetime2", nullable: false, defaultValue: new DateTime(2024, 11, 13, 13, 42, 40, 84, DateTimeKind.Local).AddTicks(3748)), + ModifiedOn = table.Column(type: "datetime2", nullable: true), + ModifiedBy = table.Column(type: "nvarchar(150)", maxLength: 150, nullable: true) }, constraints: table => { @@ -59,6 +62,89 @@ namespace Database.Migrations table.PrimaryKey("PK_Roles", x => x.Id); }); + migrationBuilder.CreateTable( + name: "CustomEvents", + schema: "dbo", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + AllianceId = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(150)", maxLength: 150, nullable: false), + Description = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: false), + IsPointsEvent = table.Column(type: "bit", nullable: false), + IsParticipationEvent = table.Column(type: "bit", nullable: false), + EventDate = table.Column(type: "datetime2", nullable: false), + CreatedBy = table.Column(type: "nvarchar(150)", maxLength: 150, nullable: false), + ModifiedOn = table.Column(type: "datetime2", nullable: true), + ModifiedBy = table.Column(type: "nvarchar(150)", maxLength: 150, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_CustomEvents", x => x.Id); + table.ForeignKey( + name: "FK_CustomEvents_Alliances_AllianceId", + column: x => x.AllianceId, + principalSchema: "dbo", + principalTable: "Alliances", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "DesertStorms", + schema: "dbo", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + AllianceId = table.Column(type: "uniqueidentifier", nullable: false), + EventDate = table.Column(type: "datetime2", nullable: false), + CreatedBy = table.Column(type: "nvarchar(150)", maxLength: 150, nullable: false), + ModifiedOn = table.Column(type: "datetime2", nullable: true), + ModifiedBy = table.Column(type: "nvarchar(150)", maxLength: 150, nullable: true), + Won = table.Column(type: "bit", nullable: false), + OpponentName = table.Column(type: "nvarchar(150)", maxLength: 150, nullable: false), + OpponentServer = table.Column(type: "int", nullable: false), + OpposingParticipants = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_DesertStorms", x => x.Id); + table.ForeignKey( + name: "FK_DesertStorms_Alliances_AllianceId", + column: x => x.AllianceId, + principalSchema: "dbo", + principalTable: "Alliances", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "MarshalGuards", + schema: "dbo", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + AllianceId = table.Column(type: "uniqueidentifier", nullable: false), + EventDate = table.Column(type: "datetime2", nullable: false), + CreatedBy = table.Column(type: "nvarchar(150)", maxLength: 150, nullable: false), + ModifiedOn = table.Column(type: "datetime2", nullable: true), + ModifiedBy = table.Column(type: "nvarchar(150)", maxLength: 150, nullable: true), + Level = table.Column(type: "int", nullable: false), + RewardPhase = table.Column(type: "int", nullable: false), + AllianceSize = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_MarshalGuards", x => x.Id); + table.ForeignKey( + name: "FK_MarshalGuards_Alliances_AllianceId", + column: x => x.AllianceId, + principalSchema: "dbo", + principalTable: "Alliances", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + migrationBuilder.CreateTable( name: "Users", schema: "dbo", @@ -93,6 +179,35 @@ namespace Database.Migrations principalColumn: "Id"); }); + migrationBuilder.CreateTable( + name: "VsDuels", + schema: "dbo", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + AllianceId = table.Column(type: "uniqueidentifier", nullable: false), + EventDate = table.Column(type: "datetime2", nullable: false), + CreatedBy = table.Column(type: "nvarchar(150)", maxLength: 150, nullable: false), + ModifiedOn = table.Column(type: "datetime2", nullable: true), + ModifiedBy = table.Column(type: "nvarchar(150)", maxLength: 150, nullable: true), + Won = table.Column(type: "bit", nullable: false), + OpponentName = table.Column(type: "nvarchar(150)", maxLength: 150, nullable: false), + OpponentServer = table.Column(type: "int", nullable: false), + OpponentPower = table.Column(type: "bigint", nullable: false), + OpponentSize = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_VsDuels", x => x.Id); + table.ForeignKey( + name: "FK_VsDuels_Alliances_AllianceId", + column: x => x.AllianceId, + principalSchema: "dbo", + principalTable: "Alliances", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + migrationBuilder.CreateTable( name: "Players", schema: "dbo", @@ -102,7 +217,11 @@ namespace Database.Migrations PlayerName = table.Column(type: "nvarchar(250)", maxLength: 250, nullable: false), RankId = table.Column(type: "uniqueidentifier", nullable: false), AllianceId = table.Column(type: "uniqueidentifier", nullable: false), - Level = table.Column(type: "nvarchar(3)", maxLength: 3, nullable: false) + Level = table.Column(type: "int", nullable: false), + CreatedOn = table.Column(type: "datetime2", nullable: false, defaultValue: new DateTime(2024, 11, 13, 13, 42, 40, 96, DateTimeKind.Local).AddTicks(5768)), + CreatedBy = table.Column(type: "nvarchar(150)", maxLength: 150, nullable: false), + ModifiedOn = table.Column(type: "datetime2", nullable: true), + ModifiedBy = table.Column(type: "nvarchar(150)", maxLength: 150, nullable: true) }, constraints: table => { @@ -247,6 +366,10 @@ namespace Database.Migrations { Id = table.Column(type: "uniqueidentifier", nullable: false), Reason = table.Column(type: "nvarchar(250)", maxLength: 250, nullable: false), + CreatedOn = table.Column(type: "datetime2", nullable: false), + CreatedBy = table.Column(type: "nvarchar(150)", maxLength: 150, nullable: false), + ModifiedOn = table.Column(type: "datetime2", nullable: true), + ModifiedBy = table.Column(type: "nvarchar(150)", maxLength: 150, nullable: true), PlayerId = table.Column(type: "uniqueidentifier", nullable: false) }, constraints: table => @@ -262,51 +385,92 @@ namespace Database.Migrations }); migrationBuilder.CreateTable( - name: "DesertStorms", + name: "CustomEventParticipants", schema: "dbo", columns: table => new { Id = table.Column(type: "uniqueidentifier", nullable: false), - Registered = table.Column(type: "bit", nullable: false), - Participated = table.Column(type: "bit", nullable: false), - Year = table.Column(type: "int", nullable: false), - CalendarWeek = table.Column(type: "int", nullable: false), - PlayerId = table.Column(type: "uniqueidentifier", nullable: false) + PlayerId = table.Column(type: "uniqueidentifier", nullable: false), + CustomEventId = table.Column(type: "uniqueidentifier", nullable: false), + Participated = table.Column(type: "bit", nullable: true), + AchievedPoints = table.Column(type: "int", nullable: true) }, constraints: table => { - table.PrimaryKey("PK_DesertStorms", x => x.Id); + table.PrimaryKey("PK_CustomEventParticipants", x => x.Id); table.ForeignKey( - name: "FK_DesertStorms_Players_PlayerId", + name: "FK_CustomEventParticipants_CustomEvents_CustomEventId", + column: x => x.CustomEventId, + principalSchema: "dbo", + principalTable: "CustomEvents", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_CustomEventParticipants_Players_PlayerId", column: x => x.PlayerId, principalSchema: "dbo", principalTable: "Players", principalColumn: "Id", - onDelete: ReferentialAction.Cascade); + onDelete: ReferentialAction.Restrict); }); migrationBuilder.CreateTable( - name: "MarshalGuards", + name: "DesertStormParticipants", schema: "dbo", columns: table => new { Id = table.Column(type: "uniqueidentifier", nullable: false), - Participated = table.Column(type: "bit", nullable: false), - Year = table.Column(type: "int", nullable: false), - Month = table.Column(type: "int", nullable: false), - Day = table.Column(type: "int", nullable: false), - PlayerId = table.Column(type: "uniqueidentifier", nullable: false) + PlayerId = table.Column(type: "uniqueidentifier", nullable: false), + DesertStormId = table.Column(type: "uniqueidentifier", nullable: false), + Registered = table.Column(type: "bit", nullable: false), + Participated = table.Column(type: "bit", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_MarshalGuards", x => x.Id); + table.PrimaryKey("PK_DesertStormParticipants", x => x.Id); table.ForeignKey( - name: "FK_MarshalGuards_Players_PlayerId", + name: "FK_DesertStormParticipants_DesertStorms_DesertStormId", + column: x => x.DesertStormId, + principalSchema: "dbo", + principalTable: "DesertStorms", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_DesertStormParticipants_Players_PlayerId", column: x => x.PlayerId, principalSchema: "dbo", principalTable: "Players", principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "MarshalGuardParticipants", + schema: "dbo", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + PlayerId = table.Column(type: "uniqueidentifier", nullable: false), + MarshalGuardId = table.Column(type: "uniqueidentifier", nullable: false), + Participated = table.Column(type: "bit", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_MarshalGuardParticipants", x => x.Id); + table.ForeignKey( + name: "FK_MarshalGuardParticipants_MarshalGuards_MarshalGuardId", + column: x => x.MarshalGuardId, + principalSchema: "dbo", + principalTable: "MarshalGuards", + principalColumn: "Id", onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_MarshalGuardParticipants_Players_PlayerId", + column: x => x.PlayerId, + principalSchema: "dbo", + principalTable: "Players", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); }); migrationBuilder.CreateTable( @@ -316,6 +480,10 @@ namespace Database.Migrations { Id = table.Column(type: "uniqueidentifier", nullable: false), PlayerNote = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: false), + CreatedOn = table.Column(type: "datetime2", nullable: false, defaultValue: new DateTime(2024, 11, 13, 13, 42, 40, 95, DateTimeKind.Local).AddTicks(4388)), + CreatedBy = table.Column(type: "nvarchar(150)", maxLength: 150, nullable: false), + ModifiedOn = table.Column(type: "datetime2", nullable: true), + ModifiedBy = table.Column(type: "nvarchar(150)", maxLength: 150, nullable: true), PlayerId = table.Column(type: "uniqueidentifier", nullable: false) }, constraints: table => @@ -331,25 +499,31 @@ namespace Database.Migrations }); migrationBuilder.CreateTable( - name: "VsDuels", + name: "VsDuelParticipants", schema: "dbo", columns: table => new { Id = table.Column(type: "uniqueidentifier", nullable: false), - WeeklyPoints = table.Column(type: "int", nullable: false), - Year = table.Column(type: "int", nullable: false), - CalendarWeek = table.Column(type: "int", nullable: false), - PlayerId = table.Column(type: "uniqueidentifier", nullable: false) + PlayerId = table.Column(type: "uniqueidentifier", nullable: false), + VsDuelId = table.Column(type: "uniqueidentifier", nullable: false), + WeeklyPoints = table.Column(type: "int", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_VsDuels", x => x.Id); + table.PrimaryKey("PK_VsDuelParticipants", x => x.Id); table.ForeignKey( - name: "FK_VsDuels_Players_PlayerId", + name: "FK_VsDuelParticipants_Players_PlayerId", column: x => x.PlayerId, principalSchema: "dbo", principalTable: "Players", principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_VsDuelParticipants_VsDuels_VsDuelId", + column: x => x.VsDuelId, + principalSchema: "dbo", + principalTable: "VsDuels", + principalColumn: "Id", onDelete: ReferentialAction.Cascade); }); @@ -385,16 +559,58 @@ namespace Database.Migrations column: "PlayerId"); migrationBuilder.CreateIndex( - name: "IX_DesertStorms_PlayerId", + name: "IX_CustomEventParticipants_CustomEventId", schema: "dbo", - table: "DesertStorms", + table: "CustomEventParticipants", + column: "CustomEventId"); + + migrationBuilder.CreateIndex( + name: "IX_CustomEventParticipants_PlayerId", + schema: "dbo", + table: "CustomEventParticipants", column: "PlayerId"); migrationBuilder.CreateIndex( - name: "IX_MarshalGuards_PlayerId", + name: "IX_CustomEvents_AllianceId", + schema: "dbo", + table: "CustomEvents", + column: "AllianceId"); + + migrationBuilder.CreateIndex( + name: "IX_DesertStormParticipants_DesertStormId", + schema: "dbo", + table: "DesertStormParticipants", + column: "DesertStormId"); + + migrationBuilder.CreateIndex( + name: "IX_DesertStormParticipants_PlayerId", + schema: "dbo", + table: "DesertStormParticipants", + column: "PlayerId"); + + migrationBuilder.CreateIndex( + name: "IX_DesertStorms_AllianceId", + schema: "dbo", + table: "DesertStorms", + column: "AllianceId"); + + migrationBuilder.CreateIndex( + name: "IX_MarshalGuardParticipants_MarshalGuardId", + schema: "dbo", + table: "MarshalGuardParticipants", + column: "MarshalGuardId"); + + migrationBuilder.CreateIndex( + name: "IX_MarshalGuardParticipants_PlayerId", + schema: "dbo", + table: "MarshalGuardParticipants", + column: "PlayerId"); + + migrationBuilder.CreateIndex( + name: "IX_MarshalGuards_AllianceId", schema: "dbo", table: "MarshalGuards", - column: "PlayerId"); + column: "AllianceId"); migrationBuilder.CreateIndex( name: "IX_Notes_PlayerId", @@ -467,10 +683,22 @@ namespace Database.Migrations filter: "[NormalizedUserName] IS NOT NULL"); migrationBuilder.CreateIndex( - name: "IX_VsDuels_PlayerId", + name: "IX_VsDuelParticipants_PlayerId", + schema: "dbo", + table: "VsDuelParticipants", + column: "PlayerId"); + + migrationBuilder.CreateIndex( + name: "IX_VsDuelParticipants_VsDuelId", + schema: "dbo", + table: "VsDuelParticipants", + column: "VsDuelId"); + + migrationBuilder.CreateIndex( + name: "IX_VsDuels_AllianceId", schema: "dbo", table: "VsDuels", - column: "PlayerId"); + column: "AllianceId"); } /// @@ -481,11 +709,15 @@ namespace Database.Migrations schema: "dbo"); migrationBuilder.DropTable( - name: "DesertStorms", + name: "CustomEventParticipants", schema: "dbo"); migrationBuilder.DropTable( - name: "MarshalGuards", + name: "DesertStormParticipants", + schema: "dbo"); + + migrationBuilder.DropTable( + name: "MarshalGuardParticipants", schema: "dbo"); migrationBuilder.DropTable( @@ -513,7 +745,19 @@ namespace Database.Migrations schema: "dbo"); migrationBuilder.DropTable( - name: "VsDuels", + name: "VsDuelParticipants", + schema: "dbo"); + + migrationBuilder.DropTable( + name: "CustomEvents", + schema: "dbo"); + + migrationBuilder.DropTable( + name: "DesertStorms", + schema: "dbo"); + + migrationBuilder.DropTable( + name: "MarshalGuards", schema: "dbo"); migrationBuilder.DropTable( @@ -529,12 +773,16 @@ namespace Database.Migrations schema: "dbo"); migrationBuilder.DropTable( - name: "Alliances", + name: "VsDuels", schema: "dbo"); migrationBuilder.DropTable( name: "Ranks", schema: "dbo"); + + migrationBuilder.DropTable( + name: "Alliances", + schema: "dbo"); } } } diff --git a/Database/Migrations/20240930140736_Init.Designer.cs b/Database/Migrations/20241114100107_ChangeIntToLong.Designer.cs similarity index 59% rename from Database/Migrations/20240930140736_Init.Designer.cs rename to Database/Migrations/20241114100107_ChangeIntToLong.Designer.cs index c81812a..dbba07b 100644 --- a/Database/Migrations/20240930140736_Init.Designer.cs +++ b/Database/Migrations/20241114100107_ChangeIntToLong.Designer.cs @@ -12,8 +12,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Database.Migrations { [DbContext(typeof(ApplicationContext))] - [Migration("20240930140736_Init")] - partial class Init + [Migration("20241114100107_ChangeIntToLong")] + partial class ChangeIntToLong { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -21,7 +21,7 @@ namespace Database.Migrations #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("dbo") - .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("ProductVersion", "9.0.0") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -32,6 +32,21 @@ namespace Database.Migrations .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("ModifiedBy") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); + b.Property("PlayerId") .HasColumnType("uniqueidentifier"); @@ -55,11 +70,25 @@ namespace Database.Migrations b.Property("Abbreviation") .IsRequired() - .HasColumnType("nvarchar(max)"); + .HasMaxLength(5) + .HasColumnType("nvarchar(5)"); + + b.Property("CreatedOn") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValue(new DateTime(2024, 11, 14, 11, 1, 7, 149, DateTimeKind.Local).AddTicks(7087)); + + b.Property("ModifiedBy") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); b.Property("Name") .IsRequired() - .HasColumnType("nvarchar(max)"); + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); b.Property("Server") .HasColumnType("int"); @@ -69,15 +98,134 @@ namespace Database.Migrations b.ToTable("Alliances", "dbo"); }); + modelBuilder.Entity("Database.Entities.CustomEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AllianceId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("EventDate") + .HasColumnType("datetime2"); + + b.Property("IsParticipationEvent") + .HasColumnType("bit"); + + b.Property("IsPointsEvent") + .HasColumnType("bit"); + + b.Property("ModifiedBy") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.HasKey("Id"); + + b.HasIndex("AllianceId"); + + b.ToTable("CustomEvents", "dbo"); + }); + + modelBuilder.Entity("Database.Entities.CustomEventParticipant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AchievedPoints") + .HasColumnType("bigint"); + + b.Property("CustomEventId") + .HasColumnType("uniqueidentifier"); + + b.Property("Participated") + .HasColumnType("bit"); + + b.Property("PlayerId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("CustomEventId"); + + b.HasIndex("PlayerId"); + + b.ToTable("CustomEventParticipants", "dbo"); + }); + modelBuilder.Entity("Database.Entities.DesertStorm", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); - b.Property("CalendarWeek") + b.Property("AllianceId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("EventDate") + .HasColumnType("datetime2"); + + b.Property("ModifiedBy") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); + + b.Property("OpponentName") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("OpponentServer") .HasColumnType("int"); + b.Property("OpposingParticipants") + .HasColumnType("int"); + + b.Property("Won") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AllianceId"); + + b.ToTable("DesertStorms", "dbo"); + }); + + modelBuilder.Entity("Database.Entities.DesertStormParticipant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DesertStormId") + .HasColumnType("uniqueidentifier"); + b.Property("Participated") .HasColumnType("bit"); @@ -87,14 +235,13 @@ namespace Database.Migrations b.Property("Registered") .HasColumnType("bit"); - b.Property("Year") - .HasColumnType("int"); - b.HasKey("Id"); + b.HasIndex("DesertStormId"); + b.HasIndex("PlayerId"); - b.ToTable("DesertStorms", "dbo"); + b.ToTable("DesertStormParticipants", "dbo"); }); modelBuilder.Entity("Database.Entities.MarshalGuard", b => @@ -103,26 +250,62 @@ namespace Database.Migrations .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); - b.Property("Day") + b.Property("AllianceId") + .HasColumnType("uniqueidentifier"); + + b.Property("AllianceSize") .HasColumnType("int"); - b.Property("Month") + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("EventDate") + .HasColumnType("datetime2"); + + b.Property("Level") .HasColumnType("int"); + b.Property("ModifiedBy") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); + + b.Property("RewardPhase") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("AllianceId"); + + b.ToTable("MarshalGuards", "dbo"); + }); + + modelBuilder.Entity("Database.Entities.MarshalGuardParticipant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("MarshalGuardId") + .HasColumnType("uniqueidentifier"); + b.Property("Participated") .HasColumnType("bit"); b.Property("PlayerId") .HasColumnType("uniqueidentifier"); - b.Property("Year") - .HasColumnType("int"); - b.HasKey("Id"); + b.HasIndex("MarshalGuardId"); + b.HasIndex("PlayerId"); - b.ToTable("MarshalGuards", "dbo"); + b.ToTable("MarshalGuardParticipants", "dbo"); }); modelBuilder.Entity("Database.Entities.Note", b => @@ -131,6 +314,23 @@ namespace Database.Migrations .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("CreatedOn") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValue(new DateTime(2024, 11, 14, 11, 1, 7, 161, DateTimeKind.Local).AddTicks(4992)); + + b.Property("ModifiedBy") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); + b.Property("PlayerId") .HasColumnType("uniqueidentifier"); @@ -155,10 +355,25 @@ namespace Database.Migrations b.Property("AllianceId") .HasColumnType("uniqueidentifier"); - b.Property("Level") + b.Property("CreatedBy") .IsRequired() - .HasMaxLength(3) - .HasColumnType("nvarchar(3)"); + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("CreatedOn") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValue(new DateTime(2024, 11, 14, 11, 1, 7, 162, DateTimeKind.Local).AddTicks(7710)); + + b.Property("Level") + .HasColumnType("int"); + + b.Property("ModifiedBy") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); b.Property("PlayerName") .IsRequired() @@ -302,23 +517,70 @@ namespace Database.Migrations .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); - b.Property("CalendarWeek") + b.Property("AllianceId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("EventDate") + .HasColumnType("datetime2"); + + b.Property("ModifiedBy") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); + + b.Property("OpponentName") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("OpponentPower") + .HasColumnType("bigint"); + + b.Property("OpponentServer") .HasColumnType("int"); + b.Property("OpponentSize") + .HasColumnType("int"); + + b.Property("Won") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AllianceId"); + + b.ToTable("VsDuels", "dbo"); + }); + + modelBuilder.Entity("Database.Entities.VsDuelParticipant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + b.Property("PlayerId") .HasColumnType("uniqueidentifier"); - b.Property("WeeklyPoints") - .HasColumnType("int"); + b.Property("VsDuelId") + .HasColumnType("uniqueidentifier"); - b.Property("Year") - .HasColumnType("int"); + b.Property("WeeklyPoints") + .HasColumnType("bigint"); b.HasKey("Id"); b.HasIndex("PlayerId"); - b.ToTable("VsDuels", "dbo"); + b.HasIndex("VsDuelId"); + + b.ToTable("VsDuelParticipants", "dbo"); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => @@ -489,25 +751,93 @@ namespace Database.Migrations b.Navigation("Player"); }); - modelBuilder.Entity("Database.Entities.DesertStorm", b => + modelBuilder.Entity("Database.Entities.CustomEvent", b => { - b.HasOne("Database.Entities.Player", "Player") - .WithMany("DesertStorms") - .HasForeignKey("PlayerId") + b.HasOne("Database.Entities.Alliance", "Alliance") + .WithMany("CustomEvents") + .HasForeignKey("AllianceId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + b.Navigation("Alliance"); + }); + + modelBuilder.Entity("Database.Entities.CustomEventParticipant", b => + { + b.HasOne("Database.Entities.CustomEvent", "CustomEvent") + .WithMany("CustomEventParticipants") + .HasForeignKey("CustomEventId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Database.Entities.Player", "Player") + .WithMany("CustomEventParticipants") + .HasForeignKey("PlayerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CustomEvent"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Database.Entities.DesertStorm", b => + { + b.HasOne("Database.Entities.Alliance", "Alliance") + .WithMany("DesertStorms") + .HasForeignKey("AllianceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Alliance"); + }); + + modelBuilder.Entity("Database.Entities.DesertStormParticipant", b => + { + b.HasOne("Database.Entities.DesertStorm", "DesertStorm") + .WithMany("DesertStormParticipants") + .HasForeignKey("DesertStormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Database.Entities.Player", "Player") + .WithMany("DesertStormParticipants") + .HasForeignKey("PlayerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("DesertStorm"); + b.Navigation("Player"); }); modelBuilder.Entity("Database.Entities.MarshalGuard", b => { - b.HasOne("Database.Entities.Player", "Player") + b.HasOne("Database.Entities.Alliance", "Alliance") .WithMany("MarshalGuards") - .HasForeignKey("PlayerId") + .HasForeignKey("AllianceId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + b.Navigation("Alliance"); + }); + + modelBuilder.Entity("Database.Entities.MarshalGuardParticipant", b => + { + b.HasOne("Database.Entities.MarshalGuard", "MarshalGuard") + .WithMany("MarshalGuardParticipants") + .HasForeignKey("MarshalGuardId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Database.Entities.Player", "Player") + .WithMany("MarshalGuardParticipants") + .HasForeignKey("PlayerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("MarshalGuard"); + b.Navigation("Player"); }); @@ -554,13 +884,32 @@ namespace Database.Migrations modelBuilder.Entity("Database.Entities.VsDuel", b => { - b.HasOne("Database.Entities.Player", "Player") + b.HasOne("Database.Entities.Alliance", "Alliance") .WithMany("VsDuels") + .HasForeignKey("AllianceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Alliance"); + }); + + modelBuilder.Entity("Database.Entities.VsDuelParticipant", b => + { + b.HasOne("Database.Entities.Player", "Player") + .WithMany("VsDuelParticipants") .HasForeignKey("PlayerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Database.Entities.VsDuel", "VsDuel") + .WithMany("VsDuelParticipants") + .HasForeignKey("VsDuelId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); b.Navigation("Player"); + + b.Navigation("VsDuel"); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => @@ -616,28 +965,58 @@ namespace Database.Migrations modelBuilder.Entity("Database.Entities.Alliance", b => { + b.Navigation("CustomEvents"); + + b.Navigation("DesertStorms"); + + b.Navigation("MarshalGuards"); + b.Navigation("Players"); b.Navigation("Users"); + + b.Navigation("VsDuels"); + }); + + modelBuilder.Entity("Database.Entities.CustomEvent", b => + { + b.Navigation("CustomEventParticipants"); + }); + + modelBuilder.Entity("Database.Entities.DesertStorm", b => + { + b.Navigation("DesertStormParticipants"); + }); + + modelBuilder.Entity("Database.Entities.MarshalGuard", b => + { + b.Navigation("MarshalGuardParticipants"); }); modelBuilder.Entity("Database.Entities.Player", b => { b.Navigation("Admonitions"); - b.Navigation("DesertStorms"); + b.Navigation("CustomEventParticipants"); - b.Navigation("MarshalGuards"); + b.Navigation("DesertStormParticipants"); + + b.Navigation("MarshalGuardParticipants"); b.Navigation("Notes"); - b.Navigation("VsDuels"); + b.Navigation("VsDuelParticipants"); }); modelBuilder.Entity("Database.Entities.Rank", b => { b.Navigation("Players"); }); + + modelBuilder.Entity("Database.Entities.VsDuel", b => + { + b.Navigation("VsDuelParticipants"); + }); #pragma warning restore 612, 618 } } diff --git a/Database/Migrations/20241114100107_ChangeIntToLong.cs b/Database/Migrations/20241114100107_ChangeIntToLong.cs new file mode 100644 index 0000000..74fbfac --- /dev/null +++ b/Database/Migrations/20241114100107_ChangeIntToLong.cs @@ -0,0 +1,123 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Database.Migrations +{ + /// + public partial class ChangeIntToLong : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "WeeklyPoints", + schema: "dbo", + table: "VsDuelParticipants", + type: "bigint", + nullable: false, + oldClrType: typeof(int), + oldType: "int"); + + migrationBuilder.AlterColumn( + name: "CreatedOn", + schema: "dbo", + table: "Players", + type: "datetime2", + nullable: false, + defaultValue: new DateTime(2024, 11, 14, 11, 1, 7, 162, DateTimeKind.Local).AddTicks(7710), + oldClrType: typeof(DateTime), + oldType: "datetime2", + oldDefaultValue: new DateTime(2024, 11, 13, 13, 42, 40, 96, DateTimeKind.Local).AddTicks(5768)); + + migrationBuilder.AlterColumn( + name: "CreatedOn", + schema: "dbo", + table: "Notes", + type: "datetime2", + nullable: false, + defaultValue: new DateTime(2024, 11, 14, 11, 1, 7, 161, DateTimeKind.Local).AddTicks(4992), + oldClrType: typeof(DateTime), + oldType: "datetime2", + oldDefaultValue: new DateTime(2024, 11, 13, 13, 42, 40, 95, DateTimeKind.Local).AddTicks(4388)); + + migrationBuilder.AlterColumn( + name: "AchievedPoints", + schema: "dbo", + table: "CustomEventParticipants", + type: "bigint", + nullable: true, + oldClrType: typeof(int), + oldType: "int", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "CreatedOn", + schema: "dbo", + table: "Alliances", + type: "datetime2", + nullable: false, + defaultValue: new DateTime(2024, 11, 14, 11, 1, 7, 149, DateTimeKind.Local).AddTicks(7087), + oldClrType: typeof(DateTime), + oldType: "datetime2", + oldDefaultValue: new DateTime(2024, 11, 13, 13, 42, 40, 84, DateTimeKind.Local).AddTicks(3748)); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "WeeklyPoints", + schema: "dbo", + table: "VsDuelParticipants", + type: "int", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint"); + + migrationBuilder.AlterColumn( + name: "CreatedOn", + schema: "dbo", + table: "Players", + type: "datetime2", + nullable: false, + defaultValue: new DateTime(2024, 11, 13, 13, 42, 40, 96, DateTimeKind.Local).AddTicks(5768), + oldClrType: typeof(DateTime), + oldType: "datetime2", + oldDefaultValue: new DateTime(2024, 11, 14, 11, 1, 7, 162, DateTimeKind.Local).AddTicks(7710)); + + migrationBuilder.AlterColumn( + name: "CreatedOn", + schema: "dbo", + table: "Notes", + type: "datetime2", + nullable: false, + defaultValue: new DateTime(2024, 11, 13, 13, 42, 40, 95, DateTimeKind.Local).AddTicks(4388), + oldClrType: typeof(DateTime), + oldType: "datetime2", + oldDefaultValue: new DateTime(2024, 11, 14, 11, 1, 7, 161, DateTimeKind.Local).AddTicks(4992)); + + migrationBuilder.AlterColumn( + name: "AchievedPoints", + schema: "dbo", + table: "CustomEventParticipants", + type: "int", + nullable: true, + oldClrType: typeof(long), + oldType: "bigint", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "CreatedOn", + schema: "dbo", + table: "Alliances", + type: "datetime2", + nullable: false, + defaultValue: new DateTime(2024, 11, 13, 13, 42, 40, 84, DateTimeKind.Local).AddTicks(3748), + oldClrType: typeof(DateTime), + oldType: "datetime2", + oldDefaultValue: new DateTime(2024, 11, 14, 11, 1, 7, 149, DateTimeKind.Local).AddTicks(7087)); + } + } +} diff --git a/Database/Migrations/20241120092041_AddStartPlayerFlagToDesertStormParticipant.Designer.cs b/Database/Migrations/20241120092041_AddStartPlayerFlagToDesertStormParticipant.Designer.cs new file mode 100644 index 0000000..2725bbc --- /dev/null +++ b/Database/Migrations/20241120092041_AddStartPlayerFlagToDesertStormParticipant.Designer.cs @@ -0,0 +1,1026 @@ +// +using System; +using Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Database.Migrations +{ + [DbContext(typeof(ApplicationContext))] + [Migration("20241120092041_AddStartPlayerFlagToDesertStormParticipant")] + partial class AddStartPlayerFlagToDesertStormParticipant + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("dbo") + .HasAnnotation("ProductVersion", "9.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Database.Entities.Admonition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("ModifiedBy") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); + + b.Property("PlayerId") + .HasColumnType("uniqueidentifier"); + + b.Property("Reason") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("nvarchar(250)"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.ToTable("Admonitions", "dbo"); + }); + + modelBuilder.Entity("Database.Entities.Alliance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Abbreviation") + .IsRequired() + .HasMaxLength(5) + .HasColumnType("nvarchar(5)"); + + b.Property("CreatedOn") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValue(new DateTime(2024, 11, 20, 10, 20, 41, 211, DateTimeKind.Local).AddTicks(7279)); + + b.Property("ModifiedBy") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Server") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Alliances", "dbo"); + }); + + modelBuilder.Entity("Database.Entities.CustomEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AllianceId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("EventDate") + .HasColumnType("datetime2"); + + b.Property("IsParticipationEvent") + .HasColumnType("bit"); + + b.Property("IsPointsEvent") + .HasColumnType("bit"); + + b.Property("ModifiedBy") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.HasKey("Id"); + + b.HasIndex("AllianceId"); + + b.ToTable("CustomEvents", "dbo"); + }); + + modelBuilder.Entity("Database.Entities.CustomEventParticipant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AchievedPoints") + .HasColumnType("bigint"); + + b.Property("CustomEventId") + .HasColumnType("uniqueidentifier"); + + b.Property("Participated") + .HasColumnType("bit"); + + b.Property("PlayerId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("CustomEventId"); + + b.HasIndex("PlayerId"); + + b.ToTable("CustomEventParticipants", "dbo"); + }); + + modelBuilder.Entity("Database.Entities.DesertStorm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AllianceId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("EventDate") + .HasColumnType("datetime2"); + + b.Property("ModifiedBy") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); + + b.Property("OpponentName") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("OpponentServer") + .HasColumnType("int"); + + b.Property("OpposingParticipants") + .HasColumnType("int"); + + b.Property("Won") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AllianceId"); + + b.ToTable("DesertStorms", "dbo"); + }); + + modelBuilder.Entity("Database.Entities.DesertStormParticipant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DesertStormId") + .HasColumnType("uniqueidentifier"); + + b.Property("Participated") + .HasColumnType("bit"); + + b.Property("PlayerId") + .HasColumnType("uniqueidentifier"); + + b.Property("Registered") + .HasColumnType("bit"); + + b.Property("StartPlayer") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("DesertStormId"); + + b.HasIndex("PlayerId"); + + b.ToTable("DesertStormParticipants", "dbo"); + }); + + modelBuilder.Entity("Database.Entities.MarshalGuard", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AllianceId") + .HasColumnType("uniqueidentifier"); + + b.Property("AllianceSize") + .HasColumnType("int"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("EventDate") + .HasColumnType("datetime2"); + + b.Property("Level") + .HasColumnType("int"); + + b.Property("ModifiedBy") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); + + b.Property("RewardPhase") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("AllianceId"); + + b.ToTable("MarshalGuards", "dbo"); + }); + + modelBuilder.Entity("Database.Entities.MarshalGuardParticipant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("MarshalGuardId") + .HasColumnType("uniqueidentifier"); + + b.Property("Participated") + .HasColumnType("bit"); + + b.Property("PlayerId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("MarshalGuardId"); + + b.HasIndex("PlayerId"); + + b.ToTable("MarshalGuardParticipants", "dbo"); + }); + + modelBuilder.Entity("Database.Entities.Note", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("CreatedOn") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValue(new DateTime(2024, 11, 20, 10, 20, 41, 225, DateTimeKind.Local).AddTicks(5440)); + + b.Property("ModifiedBy") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); + + b.Property("PlayerId") + .HasColumnType("uniqueidentifier"); + + b.Property("PlayerNote") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.ToTable("Notes", "dbo"); + }); + + modelBuilder.Entity("Database.Entities.Player", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AllianceId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("CreatedOn") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValue(new DateTime(2024, 11, 20, 10, 20, 41, 226, DateTimeKind.Local).AddTicks(9868)); + + b.Property("Level") + .HasColumnType("int"); + + b.Property("ModifiedBy") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); + + b.Property("PlayerName") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("nvarchar(250)"); + + b.Property("RankId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("AllianceId"); + + b.HasIndex("RankId"); + + b.ToTable("Players", "dbo"); + }); + + modelBuilder.Entity("Database.Entities.Rank", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("nvarchar(2)"); + + b.HasKey("Id"); + + b.ToTable("Ranks", "dbo"); + + b.HasData( + new + { + Id = new Guid("b1c10a1c-5cf3-4e22-9fc1-d9b165b85dd3"), + Name = "R5" + }, + new + { + Id = new Guid("0fc2f68a-0a4d-4922-981e-c624e4c39024"), + Name = "R4" + }, + new + { + Id = new Guid("4970e1f5-f7f5-43e8-88cc-7f8fc4075418"), + Name = "R3" + }, + new + { + Id = new Guid("d8d0c587-f269-45ff-b13e-4631298bf0af"), + Name = "R2" + }, + new + { + Id = new Guid("326edef0-5074-43a5-9db9-edc71221a0f7"), + Name = "R1" + }); + }); + + modelBuilder.Entity("Database.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("AllianceId") + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("PlayerName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("AllianceId"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("Users", "dbo"); + }); + + modelBuilder.Entity("Database.Entities.VsDuel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AllianceId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("EventDate") + .HasColumnType("datetime2"); + + b.Property("ModifiedBy") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); + + b.Property("OpponentName") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("OpponentPower") + .HasColumnType("bigint"); + + b.Property("OpponentServer") + .HasColumnType("int"); + + b.Property("OpponentSize") + .HasColumnType("int"); + + b.Property("Won") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AllianceId"); + + b.ToTable("VsDuels", "dbo"); + }); + + modelBuilder.Entity("Database.Entities.VsDuelParticipant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("PlayerId") + .HasColumnType("uniqueidentifier"); + + b.Property("VsDuelId") + .HasColumnType("uniqueidentifier"); + + b.Property("WeeklyPoints") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.HasIndex("VsDuelId"); + + b.ToTable("VsDuelParticipants", "dbo"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("Roles", "dbo"); + + b.HasData( + new + { + Id = new Guid("d8b9f882-95f0-4ba0-80ed-9c22c27ac88a"), + Name = "SystemAdministrator", + NormalizedName = "SYSTEMADMINISTRATOR" + }, + new + { + Id = new Guid("47de05ba-ff1e-46b6-9995-269084006c24"), + Name = "Administrator", + NormalizedName = "ADMINISTRATOR" + }, + new + { + Id = new Guid("5cc27946-5601-4a25-b9a9-75b8a11c0cf4"), + Name = "User", + NormalizedName = "USER" + }, + new + { + Id = new Guid("207bb0a3-ad50-49bb-bc41-b266fce66529"), + Name = "ReadOnly", + NormalizedName = "READONLY" + }); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("RoleClaims", "dbo"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserClaims", "dbo"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("UserLogins", "dbo"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("UserRoles", "dbo"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("UserTokens", "dbo"); + }); + + modelBuilder.Entity("Database.Entities.Admonition", b => + { + b.HasOne("Database.Entities.Player", "Player") + .WithMany("Admonitions") + .HasForeignKey("PlayerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Database.Entities.CustomEvent", b => + { + b.HasOne("Database.Entities.Alliance", "Alliance") + .WithMany("CustomEvents") + .HasForeignKey("AllianceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Alliance"); + }); + + modelBuilder.Entity("Database.Entities.CustomEventParticipant", b => + { + b.HasOne("Database.Entities.CustomEvent", "CustomEvent") + .WithMany("CustomEventParticipants") + .HasForeignKey("CustomEventId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Database.Entities.Player", "Player") + .WithMany("CustomEventParticipants") + .HasForeignKey("PlayerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CustomEvent"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Database.Entities.DesertStorm", b => + { + b.HasOne("Database.Entities.Alliance", "Alliance") + .WithMany("DesertStorms") + .HasForeignKey("AllianceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Alliance"); + }); + + modelBuilder.Entity("Database.Entities.DesertStormParticipant", b => + { + b.HasOne("Database.Entities.DesertStorm", "DesertStorm") + .WithMany("DesertStormParticipants") + .HasForeignKey("DesertStormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Database.Entities.Player", "Player") + .WithMany("DesertStormParticipants") + .HasForeignKey("PlayerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("DesertStorm"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Database.Entities.MarshalGuard", b => + { + b.HasOne("Database.Entities.Alliance", "Alliance") + .WithMany("MarshalGuards") + .HasForeignKey("AllianceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Alliance"); + }); + + modelBuilder.Entity("Database.Entities.MarshalGuardParticipant", b => + { + b.HasOne("Database.Entities.MarshalGuard", "MarshalGuard") + .WithMany("MarshalGuardParticipants") + .HasForeignKey("MarshalGuardId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Database.Entities.Player", "Player") + .WithMany("MarshalGuardParticipants") + .HasForeignKey("PlayerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("MarshalGuard"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Database.Entities.Note", b => + { + b.HasOne("Database.Entities.Player", "Player") + .WithMany("Notes") + .HasForeignKey("PlayerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Database.Entities.Player", b => + { + b.HasOne("Database.Entities.Alliance", "Alliance") + .WithMany("Players") + .HasForeignKey("AllianceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Database.Entities.Rank", "Rank") + .WithMany("Players") + .HasForeignKey("RankId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Alliance"); + + b.Navigation("Rank"); + }); + + modelBuilder.Entity("Database.Entities.User", b => + { + b.HasOne("Database.Entities.Alliance", "Alliance") + .WithMany("Users") + .HasForeignKey("AllianceId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Alliance"); + }); + + modelBuilder.Entity("Database.Entities.VsDuel", b => + { + b.HasOne("Database.Entities.Alliance", "Alliance") + .WithMany("VsDuels") + .HasForeignKey("AllianceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Alliance"); + }); + + modelBuilder.Entity("Database.Entities.VsDuelParticipant", b => + { + b.HasOne("Database.Entities.Player", "Player") + .WithMany("VsDuelParticipants") + .HasForeignKey("PlayerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Database.Entities.VsDuel", "VsDuel") + .WithMany("VsDuelParticipants") + .HasForeignKey("VsDuelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Player"); + + b.Navigation("VsDuel"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Database.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Database.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Database.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Database.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Database.Entities.Alliance", b => + { + b.Navigation("CustomEvents"); + + b.Navigation("DesertStorms"); + + b.Navigation("MarshalGuards"); + + b.Navigation("Players"); + + b.Navigation("Users"); + + b.Navigation("VsDuels"); + }); + + modelBuilder.Entity("Database.Entities.CustomEvent", b => + { + b.Navigation("CustomEventParticipants"); + }); + + modelBuilder.Entity("Database.Entities.DesertStorm", b => + { + b.Navigation("DesertStormParticipants"); + }); + + modelBuilder.Entity("Database.Entities.MarshalGuard", b => + { + b.Navigation("MarshalGuardParticipants"); + }); + + modelBuilder.Entity("Database.Entities.Player", b => + { + b.Navigation("Admonitions"); + + b.Navigation("CustomEventParticipants"); + + b.Navigation("DesertStormParticipants"); + + b.Navigation("MarshalGuardParticipants"); + + b.Navigation("Notes"); + + b.Navigation("VsDuelParticipants"); + }); + + modelBuilder.Entity("Database.Entities.Rank", b => + { + b.Navigation("Players"); + }); + + modelBuilder.Entity("Database.Entities.VsDuel", b => + { + b.Navigation("VsDuelParticipants"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Database/Migrations/20241120092041_AddStartPlayerFlagToDesertStormParticipant.cs b/Database/Migrations/20241120092041_AddStartPlayerFlagToDesertStormParticipant.cs new file mode 100644 index 0000000..9ca7dd0 --- /dev/null +++ b/Database/Migrations/20241120092041_AddStartPlayerFlagToDesertStormParticipant.cs @@ -0,0 +1,98 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Database.Migrations +{ + /// + public partial class AddStartPlayerFlagToDesertStormParticipant : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "CreatedOn", + schema: "dbo", + table: "Players", + type: "datetime2", + nullable: false, + defaultValue: new DateTime(2024, 11, 20, 10, 20, 41, 226, DateTimeKind.Local).AddTicks(9868), + oldClrType: typeof(DateTime), + oldType: "datetime2", + oldDefaultValue: new DateTime(2024, 11, 14, 11, 1, 7, 162, DateTimeKind.Local).AddTicks(7710)); + + migrationBuilder.AlterColumn( + name: "CreatedOn", + schema: "dbo", + table: "Notes", + type: "datetime2", + nullable: false, + defaultValue: new DateTime(2024, 11, 20, 10, 20, 41, 225, DateTimeKind.Local).AddTicks(5440), + oldClrType: typeof(DateTime), + oldType: "datetime2", + oldDefaultValue: new DateTime(2024, 11, 14, 11, 1, 7, 161, DateTimeKind.Local).AddTicks(4992)); + + migrationBuilder.AddColumn( + name: "StartPlayer", + schema: "dbo", + table: "DesertStormParticipants", + type: "bit", + nullable: false, + defaultValue: false); + + migrationBuilder.AlterColumn( + name: "CreatedOn", + schema: "dbo", + table: "Alliances", + type: "datetime2", + nullable: false, + defaultValue: new DateTime(2024, 11, 20, 10, 20, 41, 211, DateTimeKind.Local).AddTicks(7279), + oldClrType: typeof(DateTime), + oldType: "datetime2", + oldDefaultValue: new DateTime(2024, 11, 14, 11, 1, 7, 149, DateTimeKind.Local).AddTicks(7087)); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "StartPlayer", + schema: "dbo", + table: "DesertStormParticipants"); + + migrationBuilder.AlterColumn( + name: "CreatedOn", + schema: "dbo", + table: "Players", + type: "datetime2", + nullable: false, + defaultValue: new DateTime(2024, 11, 14, 11, 1, 7, 162, DateTimeKind.Local).AddTicks(7710), + oldClrType: typeof(DateTime), + oldType: "datetime2", + oldDefaultValue: new DateTime(2024, 11, 20, 10, 20, 41, 226, DateTimeKind.Local).AddTicks(9868)); + + migrationBuilder.AlterColumn( + name: "CreatedOn", + schema: "dbo", + table: "Notes", + type: "datetime2", + nullable: false, + defaultValue: new DateTime(2024, 11, 14, 11, 1, 7, 161, DateTimeKind.Local).AddTicks(4992), + oldClrType: typeof(DateTime), + oldType: "datetime2", + oldDefaultValue: new DateTime(2024, 11, 20, 10, 20, 41, 225, DateTimeKind.Local).AddTicks(5440)); + + migrationBuilder.AlterColumn( + name: "CreatedOn", + schema: "dbo", + table: "Alliances", + type: "datetime2", + nullable: false, + defaultValue: new DateTime(2024, 11, 14, 11, 1, 7, 149, DateTimeKind.Local).AddTicks(7087), + oldClrType: typeof(DateTime), + oldType: "datetime2", + oldDefaultValue: new DateTime(2024, 11, 20, 10, 20, 41, 211, DateTimeKind.Local).AddTicks(7279)); + } + } +} diff --git a/Database/Migrations/ApplicationContextModelSnapshot.cs b/Database/Migrations/ApplicationContextModelSnapshot.cs index 71fae80..7eae5cb 100644 --- a/Database/Migrations/ApplicationContextModelSnapshot.cs +++ b/Database/Migrations/ApplicationContextModelSnapshot.cs @@ -18,7 +18,7 @@ namespace Database.Migrations #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("dbo") - .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("ProductVersion", "9.0.0") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -29,6 +29,21 @@ namespace Database.Migrations .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("ModifiedBy") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); + b.Property("PlayerId") .HasColumnType("uniqueidentifier"); @@ -55,6 +70,18 @@ namespace Database.Migrations .HasMaxLength(5) .HasColumnType("nvarchar(5)"); + b.Property("CreatedOn") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValue(new DateTime(2024, 11, 20, 10, 20, 41, 211, DateTimeKind.Local).AddTicks(7279)); + + b.Property("ModifiedBy") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); + b.Property("Name") .IsRequired() .HasMaxLength(200) @@ -68,15 +95,134 @@ namespace Database.Migrations b.ToTable("Alliances", "dbo"); }); + modelBuilder.Entity("Database.Entities.CustomEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AllianceId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("EventDate") + .HasColumnType("datetime2"); + + b.Property("IsParticipationEvent") + .HasColumnType("bit"); + + b.Property("IsPointsEvent") + .HasColumnType("bit"); + + b.Property("ModifiedBy") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.HasKey("Id"); + + b.HasIndex("AllianceId"); + + b.ToTable("CustomEvents", "dbo"); + }); + + modelBuilder.Entity("Database.Entities.CustomEventParticipant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AchievedPoints") + .HasColumnType("bigint"); + + b.Property("CustomEventId") + .HasColumnType("uniqueidentifier"); + + b.Property("Participated") + .HasColumnType("bit"); + + b.Property("PlayerId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("CustomEventId"); + + b.HasIndex("PlayerId"); + + b.ToTable("CustomEventParticipants", "dbo"); + }); + modelBuilder.Entity("Database.Entities.DesertStorm", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); - b.Property("CalendarWeek") + b.Property("AllianceId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("EventDate") + .HasColumnType("datetime2"); + + b.Property("ModifiedBy") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); + + b.Property("OpponentName") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("OpponentServer") .HasColumnType("int"); + b.Property("OpposingParticipants") + .HasColumnType("int"); + + b.Property("Won") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AllianceId"); + + b.ToTable("DesertStorms", "dbo"); + }); + + modelBuilder.Entity("Database.Entities.DesertStormParticipant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DesertStormId") + .HasColumnType("uniqueidentifier"); + b.Property("Participated") .HasColumnType("bit"); @@ -86,14 +232,16 @@ namespace Database.Migrations b.Property("Registered") .HasColumnType("bit"); - b.Property("Year") - .HasColumnType("int"); + b.Property("StartPlayer") + .HasColumnType("bit"); b.HasKey("Id"); + b.HasIndex("DesertStormId"); + b.HasIndex("PlayerId"); - b.ToTable("DesertStorms", "dbo"); + b.ToTable("DesertStormParticipants", "dbo"); }); modelBuilder.Entity("Database.Entities.MarshalGuard", b => @@ -102,26 +250,62 @@ namespace Database.Migrations .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); - b.Property("Day") + b.Property("AllianceId") + .HasColumnType("uniqueidentifier"); + + b.Property("AllianceSize") .HasColumnType("int"); - b.Property("Month") + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("EventDate") + .HasColumnType("datetime2"); + + b.Property("Level") .HasColumnType("int"); + b.Property("ModifiedBy") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); + + b.Property("RewardPhase") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("AllianceId"); + + b.ToTable("MarshalGuards", "dbo"); + }); + + modelBuilder.Entity("Database.Entities.MarshalGuardParticipant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("MarshalGuardId") + .HasColumnType("uniqueidentifier"); + b.Property("Participated") .HasColumnType("bit"); b.Property("PlayerId") .HasColumnType("uniqueidentifier"); - b.Property("Year") - .HasColumnType("int"); - b.HasKey("Id"); + b.HasIndex("MarshalGuardId"); + b.HasIndex("PlayerId"); - b.ToTable("MarshalGuards", "dbo"); + b.ToTable("MarshalGuardParticipants", "dbo"); }); modelBuilder.Entity("Database.Entities.Note", b => @@ -130,6 +314,23 @@ namespace Database.Migrations .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("CreatedOn") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValue(new DateTime(2024, 11, 20, 10, 20, 41, 225, DateTimeKind.Local).AddTicks(5440)); + + b.Property("ModifiedBy") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); + b.Property("PlayerId") .HasColumnType("uniqueidentifier"); @@ -154,10 +355,25 @@ namespace Database.Migrations b.Property("AllianceId") .HasColumnType("uniqueidentifier"); - b.Property("Level") + b.Property("CreatedBy") .IsRequired() - .HasMaxLength(3) - .HasColumnType("nvarchar(3)"); + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("CreatedOn") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValue(new DateTime(2024, 11, 20, 10, 20, 41, 226, DateTimeKind.Local).AddTicks(9868)); + + b.Property("Level") + .HasColumnType("int"); + + b.Property("ModifiedBy") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); b.Property("PlayerName") .IsRequired() @@ -301,23 +517,70 @@ namespace Database.Migrations .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); - b.Property("CalendarWeek") + b.Property("AllianceId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("EventDate") + .HasColumnType("datetime2"); + + b.Property("ModifiedBy") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); + + b.Property("OpponentName") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("OpponentPower") + .HasColumnType("bigint"); + + b.Property("OpponentServer") .HasColumnType("int"); + b.Property("OpponentSize") + .HasColumnType("int"); + + b.Property("Won") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AllianceId"); + + b.ToTable("VsDuels", "dbo"); + }); + + modelBuilder.Entity("Database.Entities.VsDuelParticipant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + b.Property("PlayerId") .HasColumnType("uniqueidentifier"); - b.Property("WeeklyPoints") - .HasColumnType("int"); + b.Property("VsDuelId") + .HasColumnType("uniqueidentifier"); - b.Property("Year") - .HasColumnType("int"); + b.Property("WeeklyPoints") + .HasColumnType("bigint"); b.HasKey("Id"); b.HasIndex("PlayerId"); - b.ToTable("VsDuels", "dbo"); + b.HasIndex("VsDuelId"); + + b.ToTable("VsDuelParticipants", "dbo"); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => @@ -488,25 +751,93 @@ namespace Database.Migrations b.Navigation("Player"); }); - modelBuilder.Entity("Database.Entities.DesertStorm", b => + modelBuilder.Entity("Database.Entities.CustomEvent", b => { - b.HasOne("Database.Entities.Player", "Player") - .WithMany("DesertStorms") - .HasForeignKey("PlayerId") + b.HasOne("Database.Entities.Alliance", "Alliance") + .WithMany("CustomEvents") + .HasForeignKey("AllianceId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + b.Navigation("Alliance"); + }); + + modelBuilder.Entity("Database.Entities.CustomEventParticipant", b => + { + b.HasOne("Database.Entities.CustomEvent", "CustomEvent") + .WithMany("CustomEventParticipants") + .HasForeignKey("CustomEventId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Database.Entities.Player", "Player") + .WithMany("CustomEventParticipants") + .HasForeignKey("PlayerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CustomEvent"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("Database.Entities.DesertStorm", b => + { + b.HasOne("Database.Entities.Alliance", "Alliance") + .WithMany("DesertStorms") + .HasForeignKey("AllianceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Alliance"); + }); + + modelBuilder.Entity("Database.Entities.DesertStormParticipant", b => + { + b.HasOne("Database.Entities.DesertStorm", "DesertStorm") + .WithMany("DesertStormParticipants") + .HasForeignKey("DesertStormId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Database.Entities.Player", "Player") + .WithMany("DesertStormParticipants") + .HasForeignKey("PlayerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("DesertStorm"); + b.Navigation("Player"); }); modelBuilder.Entity("Database.Entities.MarshalGuard", b => { - b.HasOne("Database.Entities.Player", "Player") + b.HasOne("Database.Entities.Alliance", "Alliance") .WithMany("MarshalGuards") - .HasForeignKey("PlayerId") + .HasForeignKey("AllianceId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + b.Navigation("Alliance"); + }); + + modelBuilder.Entity("Database.Entities.MarshalGuardParticipant", b => + { + b.HasOne("Database.Entities.MarshalGuard", "MarshalGuard") + .WithMany("MarshalGuardParticipants") + .HasForeignKey("MarshalGuardId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Database.Entities.Player", "Player") + .WithMany("MarshalGuardParticipants") + .HasForeignKey("PlayerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("MarshalGuard"); + b.Navigation("Player"); }); @@ -553,13 +884,32 @@ namespace Database.Migrations modelBuilder.Entity("Database.Entities.VsDuel", b => { - b.HasOne("Database.Entities.Player", "Player") + b.HasOne("Database.Entities.Alliance", "Alliance") .WithMany("VsDuels") + .HasForeignKey("AllianceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Alliance"); + }); + + modelBuilder.Entity("Database.Entities.VsDuelParticipant", b => + { + b.HasOne("Database.Entities.Player", "Player") + .WithMany("VsDuelParticipants") .HasForeignKey("PlayerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Database.Entities.VsDuel", "VsDuel") + .WithMany("VsDuelParticipants") + .HasForeignKey("VsDuelId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); b.Navigation("Player"); + + b.Navigation("VsDuel"); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => @@ -615,28 +965,58 @@ namespace Database.Migrations modelBuilder.Entity("Database.Entities.Alliance", b => { + b.Navigation("CustomEvents"); + + b.Navigation("DesertStorms"); + + b.Navigation("MarshalGuards"); + b.Navigation("Players"); b.Navigation("Users"); + + b.Navigation("VsDuels"); + }); + + modelBuilder.Entity("Database.Entities.CustomEvent", b => + { + b.Navigation("CustomEventParticipants"); + }); + + modelBuilder.Entity("Database.Entities.DesertStorm", b => + { + b.Navigation("DesertStormParticipants"); + }); + + modelBuilder.Entity("Database.Entities.MarshalGuard", b => + { + b.Navigation("MarshalGuardParticipants"); }); modelBuilder.Entity("Database.Entities.Player", b => { b.Navigation("Admonitions"); - b.Navigation("DesertStorms"); + b.Navigation("CustomEventParticipants"); - b.Navigation("MarshalGuards"); + b.Navigation("DesertStormParticipants"); + + b.Navigation("MarshalGuardParticipants"); b.Navigation("Notes"); - b.Navigation("VsDuels"); + b.Navigation("VsDuelParticipants"); }); modelBuilder.Entity("Database.Entities.Rank", b => { b.Navigation("Players"); }); + + modelBuilder.Entity("Database.Entities.VsDuel", b => + { + b.Navigation("VsDuelParticipants"); + }); #pragma warning restore 612, 618 } } diff --git a/Ui/angular.json b/Ui/angular.json index d53de92..cbc0a4c 100644 --- a/Ui/angular.json +++ b/Ui/angular.json @@ -27,7 +27,8 @@ "index": "src/index.html", "browser": "src/main.ts", "polyfills": [ - "zone.js" + "zone.js", + "@angular/localize/init" ], "tsConfig": "tsconfig.app.json", "assets": [ @@ -35,9 +36,10 @@ "src/assets" ], "styles": [ - "./node_modules/ngx-bootstrap/datepicker/bs-datepicker.css", - "./node_modules/bootstrap/dist/css/bootstrap.min.css", + "node_modules/bootstrap/dist/css/bootstrap.min.css", "./node_modules/bootswatch/dist/cyborg/bootstrap.min.css", + "./node_modules/ngx-spinner/animations/ball-8bits.css", + "./node_modules/ngx-toastr/toastr.css", "src/styles.css" ], "scripts": [] @@ -47,8 +49,8 @@ "budgets": [ { "type": "initial", - "maximumWarning": "500kb", - "maximumError": "1mb" + "maximumWarning": "2mb", + "maximumError": "5mb" }, { "type": "anyComponentStyle", @@ -61,7 +63,13 @@ "development": { "optimization": false, "extractLicenses": false, - "sourceMap": true + "sourceMap": true, + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.development.ts" + } + ] } }, "defaultConfiguration": "production" @@ -89,7 +97,8 @@ "options": { "polyfills": [ "zone.js", - "zone.js/testing" + "zone.js/testing", + "@angular/localize/init" ], "tsConfig": "tsconfig.spec.json", "assets": [ diff --git a/Ui/package-lock.json b/Ui/package-lock.json index 5009c53..644acf7 100644 --- a/Ui/package-lock.json +++ b/Ui/package-lock.json @@ -16,11 +16,19 @@ "@angular/platform-browser": "^18.2.7", "@angular/platform-browser-dynamic": "^18.2.7", "@angular/router": "^18.2.7", - "bootstrap": "^5.2.3", + "@auth0/angular-jwt": "^5.2.0", + "@ng-bootstrap/ng-bootstrap": "^17.0.1", + "@popperjs/core": "^2.11.8", + "@sweetalert2/theme-dark": "^5.0.18", + "bootstrap": "^5.3.2", + "bootstrap-icons": "^1.11.3", "bootswatch": "^5.3.3", "jest-editor-support": "*", - "ngx-bootstrap": "^18.0.2", + "ngx-pagination": "^6.0.3", + "ngx-spinner": "^17.0.0", + "ngx-toastr": "^19.0.0", "rxjs": "~7.8.0", + "sweetalert2": "^11.14.3", "tslib": "^2.3.0", "zone.js": "~0.14.3" }, @@ -28,6 +36,7 @@ "@angular-devkit/build-angular": "^18.2.8", "@angular/cli": "^18.2.8", "@angular/compiler-cli": "^18.2.7", + "@angular/localize": "^18.2.7", "@types/jasmine": "~5.1.0", "jasmine-core": "~5.3.0", "karma": "~6.4.0", @@ -467,7 +476,6 @@ "version": "18.2.7", "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-18.2.7.tgz", "integrity": "sha512-U7cveObj+rrXH5EC8egAhATCeAAcOceEQDTVIOWmBa0qMR4hOMjtI2XUS2QRuI1Q+fQZ2hVEOW95WVLvEMsANA==", - "dev": true, "dependencies": { "@babel/core": "7.25.2", "@jridgewell/sourcemap-codec": "^1.4.14", @@ -523,6 +531,29 @@ "rxjs": "^6.5.3 || ^7.4.0" } }, + "node_modules/@angular/localize": { + "version": "18.2.7", + "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-18.2.7.tgz", + "integrity": "sha512-qYozomhO+1BlvtoMEEgKhaKz8thoztqNZEYPq9RmfkTB5uW7Q8h6rr1Sc2YAzJ6+ZA0McwabdJSX1TDxWyZx0Q==", + "dependencies": { + "@babel/core": "7.25.2", + "@types/babel__core": "7.20.5", + "fast-glob": "3.3.2", + "yargs": "^17.2.1" + }, + "bin": { + "localize-extract": "tools/bundles/src/extract/cli.js", + "localize-migrate": "tools/bundles/src/migrate/cli.js", + "localize-translate": "tools/bundles/src/translate/cli.js" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "peerDependencies": { + "@angular/compiler": "18.2.7", + "@angular/compiler-cli": "18.2.7" + } + }, "node_modules/@angular/platform-browser": { "version": "18.2.7", "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-18.2.7.tgz", @@ -578,6 +609,17 @@ "rxjs": "^6.5.3 || ^7.4.0" } }, + "node_modules/@auth0/angular-jwt": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@auth0/angular-jwt/-/angular-jwt-5.2.0.tgz", + "integrity": "sha512-9FS2L0QwGNlxA/zgeehCcsR9CZscouyXkoIj1fODM36A8BLfdzg9k9DWAXUQ2Drjk0AypGAFzeNZR4vsLMhdeQ==", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@angular/common": ">=14.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.25.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.25.7.tgz", @@ -3984,6 +4026,22 @@ "win32" ] }, + "node_modules/@ng-bootstrap/ng-bootstrap": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-17.0.1.tgz", + "integrity": "sha512-utbm8OXIoqVVYGVzQkOS773ymbjc+UMkXv8lyi7hTqLhCQs0rZ0yA74peqVZRuOGXLHgcSTA7fnJhA80iQOblw==", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": "^18.0.0", + "@angular/core": "^18.0.0", + "@angular/forms": "^18.0.0", + "@angular/localize": "^18.0.0", + "@popperjs/core": "^2.11.8", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, "node_modules/@ngtools/webpack": { "version": "18.2.8", "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-18.2.8.tgz", @@ -4004,7 +4062,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -4017,7 +4074,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, "engines": { "node": ">= 8" } @@ -4026,7 +4082,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -4358,7 +4413,6 @@ "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", - "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -4679,6 +4733,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@sweetalert2/theme-dark": { + "version": "5.0.18", + "resolved": "https://registry.npmjs.org/@sweetalert2/theme-dark/-/theme-dark-5.0.18.tgz", + "integrity": "sha512-Fdt8OQHQcbJy6i+rvA49h3OAzQevMwDgfsHPdR2kNwT5M7AtG5rAaBBo0StlvNbcTx/AQ5xhEdMyJdnM05CNoQ==" + }, "node_modules/@tufjs/canonical-json": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", @@ -4725,6 +4784,43 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, "node_modules/@types/body-parser": { "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", @@ -5236,7 +5332,6 @@ }, "node_modules/ansi-regex": { "version": "5.0.1", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5498,7 +5593,6 @@ }, "node_modules/binary-extensions": { "version": "2.3.0", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5605,6 +5699,21 @@ "@popperjs/core": "^2.11.8" } }, + "node_modules/bootstrap-icons": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.3.tgz", + "integrity": "sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ] + }, "node_modules/bootswatch": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/bootswatch/-/bootswatch-5.3.3.tgz", @@ -5934,7 +6043,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -5958,7 +6066,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, "dependencies": { "fill-range": "^7.1.1" }, @@ -6290,7 +6397,6 @@ }, "node_modules/convert-source-map": { "version": "1.9.0", - "dev": true, "license": "MIT" }, "node_modules/cookie": { @@ -6798,7 +6904,6 @@ }, "node_modules/emoji-regex": { "version": "8.0.0", - "dev": true, "license": "MIT" }, "node_modules/emojis-list": { @@ -7305,7 +7410,6 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -7332,7 +7436,6 @@ "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", - "dev": true, "dependencies": { "reusify": "^1.0.4" } @@ -7565,7 +7668,6 @@ }, "node_modules/get-caller-file": { "version": "2.0.5", - "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -7644,7 +7746,6 @@ }, "node_modules/glob-parent": { "version": "5.1.2", - "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -8172,7 +8273,6 @@ }, "node_modules/is-binary-path": { "version": "2.1.0", - "dev": true, "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" @@ -8198,7 +8298,6 @@ }, "node_modules/is-extglob": { "version": "2.1.1", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -8206,7 +8305,6 @@ }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8214,7 +8312,6 @@ }, "node_modules/is-glob": { "version": "4.0.3", - "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -10281,7 +10378,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, "engines": { "node": ">= 8" } @@ -10699,19 +10795,42 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, - "node_modules/ngx-bootstrap": { - "version": "18.0.2", - "resolved": "https://registry.npmjs.org/ngx-bootstrap/-/ngx-bootstrap-18.0.2.tgz", - "integrity": "sha512-fmbLL4dCgPOTZxBMHpJFabHtOXM02b0atHW6queXbFnSouy5jC3UQ0tdcdD2k09H2x2aeRenMt6AcmPcwyoFSg==", + "node_modules/ngx-pagination": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/ngx-pagination/-/ngx-pagination-6.0.3.tgz", + "integrity": "sha512-lONjTQ7hFPh1SyhwDrRd5ZwM4NMGQ7bNR6vLrs6mrU0Z8Q1zCcWbf/pvyp4DOlGyd9uyZxRy2wUsSZLeIPjbAw==", "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { - "@angular/animations": "^18.0.1", - "@angular/common": "^18.0.1", - "@angular/core": "^18.0.1", - "@angular/forms": "^18.0.1", - "rxjs": "^6.5.3 || ^7.4.0" + "@angular/common": ">=13.0.0", + "@angular/core": ">=13.0.0" + } + }, + "node_modules/ngx-spinner": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/ngx-spinner/-/ngx-spinner-17.0.0.tgz", + "integrity": "sha512-VWDSvLlCnaWqu0W1L+ybQIRHTbd+GffkX1sWs++iMPXMGVJ2ZCuzW32FHnu+p0regMUHU8r1/rvUcFD0YooJxQ==", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/animations": ">=15.0.0", + "@angular/common": ">=15.0.0", + "@angular/core": ">=15.0.0" + } + }, + "node_modules/ngx-toastr": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/ngx-toastr/-/ngx-toastr-19.0.0.tgz", + "integrity": "sha512-6pTnktwwWD+kx342wuMOWB4+bkyX9221pAgGz3SHOJH0/MI9erLucS8PeeJDFwbUYyh75nQ6AzVtolgHxi52dQ==", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": ">=16.0.0-0", + "@angular/core": ">=16.0.0-0", + "@angular/platform-browser": ">=16.0.0-0" } }, "node_modules/nice-napi": { @@ -11950,7 +12069,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -12003,7 +12121,6 @@ }, "node_modules/readdirp": { "version": "3.6.0", - "dev": true, "license": "MIT", "dependencies": { "picomatch": "^2.2.1" @@ -12016,7 +12133,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "engines": { "node": ">=8.6" }, @@ -12027,8 +12143,7 @@ "node_modules/reflect-metadata": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", - "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", - "dev": true + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==" }, "node_modules/regenerate": { "version": "1.4.2", @@ -12117,7 +12232,6 @@ }, "node_modules/require-directory": { "version": "2.1.1", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -12252,7 +12366,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -12329,7 +12442,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "funding": [ { "type": "github", @@ -13104,7 +13216,6 @@ }, "node_modules/string-width": { "version": "4.2.3", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -13132,7 +13243,6 @@ }, "node_modules/strip-ansi": { "version": "6.0.1", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -13174,6 +13284,15 @@ "node": ">=8" } }, + "node_modules/sweetalert2": { + "version": "11.14.3", + "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.14.3.tgz", + "integrity": "sha512-6NuBHWJCv2gtw4y8PUXLB41hty+V6U2mKZMAvydL1IRPcORR0yuyq3cjFD/+ByrCk3muEFggbZX/x6HwmbVfbA==", + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/limonte" + } + }, "node_modules/symbol-observable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", @@ -13447,7 +13566,6 @@ "version": "5.5.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", - "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -14931,7 +15049,6 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -14949,7 +15066,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -14964,7 +15080,6 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -14978,7 +15093,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -14989,14 +15103,12 @@ "node_modules/yargs/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/yargs/node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -15013,7 +15125,6 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, "engines": { "node": ">=10" } @@ -15022,7 +15133,6 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, "engines": { "node": ">=12" } diff --git a/Ui/package.json b/Ui/package.json index 6c2c2d0..9c44e9b 100644 --- a/Ui/package.json +++ b/Ui/package.json @@ -18,10 +18,19 @@ "@angular/platform-browser": "^18.2.7", "@angular/platform-browser-dynamic": "^18.2.7", "@angular/router": "^18.2.7", + "@auth0/angular-jwt": "^5.2.0", + "@ng-bootstrap/ng-bootstrap": "^17.0.1", + "@popperjs/core": "^2.11.8", + "@sweetalert2/theme-dark": "^5.0.18", + "bootstrap": "^5.3.2", + "bootstrap-icons": "^1.11.3", "bootswatch": "^5.3.3", "jest-editor-support": "*", - "ngx-bootstrap": "^18.0.2", + "ngx-pagination": "^6.0.3", + "ngx-spinner": "^17.0.0", + "ngx-toastr": "^19.0.0", "rxjs": "~7.8.0", + "sweetalert2": "^11.14.3", "tslib": "^2.3.0", "zone.js": "~0.14.3" }, @@ -29,6 +38,7 @@ "@angular-devkit/build-angular": "^18.2.8", "@angular/cli": "^18.2.8", "@angular/compiler-cli": "^18.2.7", + "@angular/localize": "^18.2.7", "@types/jasmine": "~5.1.0", "jasmine-core": "~5.3.0", "karma": "~6.4.0", diff --git a/Ui/src/app/Authentication/email-confirmation/email-confirmation.component.css b/Ui/src/app/Authentication/email-confirmation/email-confirmation.component.css new file mode 100644 index 0000000..e69de29 diff --git a/Ui/src/app/Authentication/email-confirmation/email-confirmation.component.html b/Ui/src/app/Authentication/email-confirmation/email-confirmation.component.html new file mode 100644 index 0000000..e5f9807 --- /dev/null +++ b/Ui/src/app/Authentication/email-confirmation/email-confirmation.component.html @@ -0,0 +1,58 @@ +
+

Email confirmation

+ + @if (showSuccess) { +
+
+ Confirmation Successful! +
+
+
Email Successfully Confirmed
+

+ Thank you for confirming your email address. Your registration is now complete, and you can log in. +

+ + Go to Login + +
+
+ } + + @if (showError) { +
+
+ Confirmation Failed! +
+
+
Email Confirmation Unsuccessful
+

+ Unfortunately, the confirmation of your email address has failed. The link may have expired or is invalid. +

+

+ Please request a new confirmation email to complete the process. +

+ +
+
+ } + + @if (showResendSuccess) { +
+
+ Confirmation Email Sent! +
+
+
New Email Confirmation Sent
+

+ A new confirmation email has been successfully sent to your email address. Please check your inbox and follow the link to complete the confirmation. +

+

+ If the email does not arrive within a few minutes, please also check your spam folder. +

+
+
+ } + +
diff --git a/Ui/src/app/Authentication/email-confirmation/email-confirmation.component.ts b/Ui/src/app/Authentication/email-confirmation/email-confirmation.component.ts new file mode 100644 index 0000000..f30dcc4 --- /dev/null +++ b/Ui/src/app/Authentication/email-confirmation/email-confirmation.component.ts @@ -0,0 +1,80 @@ +import {Component, inject, OnInit} from '@angular/core'; +import {ActivatedRoute} from "@angular/router"; +import {ToastrService} from "ngx-toastr"; +import {AuthenticationService} from "../../services/authentication.service"; +import {ConfirmEmailRequestModel} from "../../models/confirmEmailRequest.model"; +import {EmailConfirmationRequestModel} from "../../models/emailConfirmationRequest.model"; +import {environment} from "../../../environments/environment"; + +@Component({ + selector: 'app-email-confirmation', + templateUrl: './email-confirmation.component.html', + styleUrl: './email-confirmation.component.css' +}) +export class EmailConfirmationComponent implements OnInit { + + private readonly _activatedRoute: ActivatedRoute = inject(ActivatedRoute); + private readonly _toastr: ToastrService = inject(ToastrService); + private readonly _authenticationService: AuthenticationService = inject(AuthenticationService); + + public showSuccess: boolean = false; + public showError: boolean = false; + public showResendSuccess: boolean = false; + + public email: string | null = null; + + ngOnInit() { + + const token = this._activatedRoute.snapshot.queryParams['token']; + this.email = this._activatedRoute.snapshot.queryParams['email']; + + if (!token || !this.email) { + this.showError = true; + return; + } + + this.confirmEmail(this.email, token); + } + + onSendNewConfirmationEmail() { + if (!this.email) { + this._toastr.error('No email address found. Please contact support for assistance.', 'Missing Email'); + } + const emailConfirmation: EmailConfirmationRequestModel = { + email: this.email!, + clientUri: environment.emailConfirmUri + }; + + this._authenticationService.resendConfirmationEmail(emailConfirmation).subscribe({ + next: ((response) => { + if (response) { + this._toastr.success('A new confirmation email has been successfully sent to your address.', 'Email Sent'); + this.showResendSuccess = true; + this.showError = false; + } + }), + error: (error) => { + console.log(error); + this._toastr.error('An error occurred while attempting to send the confirmation email. Please try again later.', 'Error Sending Email'); + } + }); + } + + confirmEmail(email: string, token: string) { + const confirmEmailRequest: ConfirmEmailRequestModel = { + email: email, + token: token + } + this._authenticationService.confirmEmail(confirmEmailRequest).subscribe({ + next: ((response) => { + if (response) { + this.showSuccess = true; + } + }), + error: (error) => { + console.log(error); + this.showError = true; + } + }); + } +} diff --git a/Ui/src/app/Authentication/forgot-password/forgot-password.component.css b/Ui/src/app/Authentication/forgot-password/forgot-password.component.css new file mode 100644 index 0000000..e69de29 diff --git a/Ui/src/app/Authentication/forgot-password/forgot-password.component.html b/Ui/src/app/Authentication/forgot-password/forgot-password.component.html new file mode 100644 index 0000000..10d69c8 --- /dev/null +++ b/Ui/src/app/Authentication/forgot-password/forgot-password.component.html @@ -0,0 +1,60 @@ +@if (forgotPasswordForm) { + + + + + + +} + + + diff --git a/Ui/src/app/Authentication/forgot-password/forgot-password.component.ts b/Ui/src/app/Authentication/forgot-password/forgot-password.component.ts new file mode 100644 index 0000000..cdac126 --- /dev/null +++ b/Ui/src/app/Authentication/forgot-password/forgot-password.component.ts @@ -0,0 +1,52 @@ +import {Component, inject} from '@angular/core'; +import {NgbActiveModal} from "@ng-bootstrap/ng-bootstrap"; +import {ToastrService} from "ngx-toastr"; +import {AuthenticationService} from "../../services/authentication.service"; +import {FormControl, FormGroup, Validators} from "@angular/forms"; +import {ForgotPasswordModel} from "../../models/forgotPassword.model"; +import {environment} from "../../../environments/environment"; + +@Component({ + selector: 'app-forgot-password', + templateUrl: './forgot-password.component.html', + styleUrl: './forgot-password.component.css' +}) +export class ForgotPasswordComponent { + + public activeModal: NgbActiveModal = inject(NgbActiveModal); + private readonly _toasterService: ToastrService = inject(ToastrService); + private readonly _authenticationService: AuthenticationService = inject(AuthenticationService); + + public showSendMessage: boolean = false; + + forgotPasswordForm: FormGroup = new FormGroup({ + email: new FormControl('', [Validators.required, Validators.email]), + }); + + get f() { + return this.forgotPasswordForm.controls; + } + + onSubmit() { + if (this.forgotPasswordForm.invalid) { + return; + } + const forgotPasswordModel: ForgotPasswordModel = { + email: this.forgotPasswordForm.get('email')?.value, + resetPasswordUri: environment.resetPasswordUrl + }; + + this._authenticationService.forgotPassword(forgotPasswordModel).subscribe({ + next: (response) => { + if (response) { + this.showSendMessage = true; + } + }, + error: (error) => { + console.log(error); + this._toasterService.error('An error occurred. Please try again later.', 'Error'); + } + }); + } + +} diff --git a/Ui/src/app/Authentication/login/login.component.css b/Ui/src/app/Authentication/login/login.component.css new file mode 100644 index 0000000..d6b9488 --- /dev/null +++ b/Ui/src/app/Authentication/login/login.component.css @@ -0,0 +1,131 @@ +.background { + position: relative; + height: 100vh; + background-position: center; + background-repeat: no-repeat; + background-size: cover; +} + +.background::before { + content: ""; + background-image: url('../../../assets/images/background.jpg'); + background-position: center; + background-repeat: no-repeat; + background-size: cover; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 0.8; + background-color: rgba(0, 0, 0, 0.4); + z-index: -1; +} + +.title { + text-align: center; + padding-top: 50px; + font-size: 3em; + color: #ffffff; + text-shadow: 2px 2px 5px rgba(0, 0, 0, 0.7); +} + + +.login-box { + background-color: rgba(0, 0, 0, 0.7); + padding: 20px; + border-radius: 10px; + width: 100%; + max-width: 500px; + margin: 100px auto 0; + box-shadow: 0 0 15px rgba(0, 0, 0, 0.5); +} + + +.login-box h2 { + text-align: center; + font-size: 2em; + color: #ffffff; + text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.5); +} + +.login-box .custom-link { + color: #a5a5f6; + font-size: 0.9rem; + cursor: pointer; +} + + +.login-box button { + width: 100%; + padding: 10px; + margin-top: 10px; + background-color: #0275d8; + border: none; + border-radius: 5px; + color: white; + font-size: 1em; + cursor: pointer; +} + +.login-box button:hover { + background-color: #025aa5; +} + +.login-box .form-control { + background-color: rgba(255, 255, 255, 0.1); + color: #ffffff; + border: 1px solid rgba(255, 255, 255, 0.4); +} + +.login-box .form-control::placeholder { + color: rgba(255, 255, 255, 0.7); +} + +.login-box .form-control.is-invalid { + border-color: #ff3860; +} + +.login-box .invalid-feedback { + color: #ff3860; +} + +/* Footer-Styling */ +.footer { + display: flex; + justify-content: space-between; /* Platz zwischen den Elementen */ + align-items: center; /* Vertikale Ausrichtung */ + width: 100%; + padding: 10px 20px; + background-color: #090909; /* Hellgrauer Hintergrund */ + color: #ffffff; /* Dezente Textfarbe */ + font-size: 0.9em; + position: absolute; /* Absolute Position */ + bottom: 0; /* Fixiert am unteren Rand */ + left: 0; + box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.1); /* Schatteneffekt */ +} + +/* Link-Styling */ +.footer a { + text-decoration: none; + color: #007bff; /* Blau für Links */ + margin-left: 10px; +} + +.footer a:hover { + text-decoration: underline; +} + + +@media (max-width: 768px) { + .login-box { + width: 90%; + margin-top: 50px; + } + + .title { + font-size: 2em; + padding-top: 30px; + } +} diff --git a/Ui/src/app/Authentication/login/login.component.html b/Ui/src/app/Authentication/login/login.component.html new file mode 100644 index 0000000..03dca3d --- /dev/null +++ b/Ui/src/app/Authentication/login/login.component.html @@ -0,0 +1,82 @@ +
+
+
Last War - Player Management
+ +
+ +
+ © {{currentYear}} Tomasi-Developing + Version: {{version}} +
+
diff --git a/Ui/src/app/Authentication/login/login.component.ts b/Ui/src/app/Authentication/login/login.component.ts new file mode 100644 index 0000000..2a77f2e --- /dev/null +++ b/Ui/src/app/Authentication/login/login.component.ts @@ -0,0 +1,71 @@ +import {Component, inject} from '@angular/core'; +import {FormControl, FormGroup, Validators} from "@angular/forms"; +import {AuthenticationService} from "../../services/authentication.service"; +import {Router} from "@angular/router"; +import {ToastrService} from "ngx-toastr"; +import {LoginRequestModel} from "../../models/login.model"; +import {environment} from "../../../environments/environment"; +import {NgbModal} from "@ng-bootstrap/ng-bootstrap"; +import {PlayerEditModalComponent} from "../../modals/player-edit-modal/player-edit-modal.component"; +import {PlayerModel} from "../../models/player.model"; +import {ForgotPasswordComponent} from "../forgot-password/forgot-password.component"; + +@Component({ + selector: 'app-login', + templateUrl: './login.component.html', + styleUrl: './login.component.css' +}) +export class LoginComponent { + + public isPasswordType: boolean = true; + public currentYear: number = new Date().getFullYear(); + public version: string = environment.version; + + public loginForm: FormGroup = new FormGroup({ + email: new FormControl('', [Validators.required, Validators.email]), + password: new FormControl('', [Validators.required]), + }); + + private readonly _authenticationService: AuthenticationService = inject(AuthenticationService); + private readonly _router: Router = inject(Router); + private readonly _toastr: ToastrService = inject(ToastrService); + private readonly _modalService : NgbModal = inject(NgbModal); + + get email() { + return this.loginForm.get('email'); + } + + get password() { + return this.loginForm.get('password'); + } + + onLogin(): void { + if (this.loginForm.invalid) { + return; + } + + const loginRequest: LoginRequestModel = this.loginForm.value as LoginRequestModel; + + this._authenticationService.login(loginRequest).subscribe({ + next: ((response) => { + if (response) { + this._router.navigate(['/']).then(); + } + }), + error: ((error) => { + console.log(error); + this._toastr.error(error.error.name ?? 'An error occurred while trying to log in', 'Login'); + }) + }); + } + + onForgotPassword(): void { + this._modalService.open(ForgotPasswordComponent, + {animation: true, backdrop: 'static', centered: true, size: 'lg'}); + } + + onSignUp() { + this._router.navigate(['sign-up']).then(); + } + +} diff --git a/Ui/src/app/Authentication/register/register.component.css b/Ui/src/app/Authentication/register/register.component.css new file mode 100644 index 0000000..e69de29 diff --git a/Ui/src/app/Authentication/register/register.component.html b/Ui/src/app/Authentication/register/register.component.html new file mode 100644 index 0000000..e2bf256 --- /dev/null +++ b/Ui/src/app/Authentication/register/register.component.html @@ -0,0 +1,149 @@ +
+

Register

+ + @if (showError) { +
+
+ Registration Failed! +
+
+
An Error Occurred
+

+ The registration could not be completed. Some required information is missing, or a technical issue has occurred. +

+

+ Please check the registration link or contact support if the issue persists. +

+
+
+ } + + @if (showSuccess) { +
+
+ Successfully Registered! +
+
+
Welcome, {{playerName}}!
+

+ You have successfully registered. +

+

+ A confirmation email has been sent to the email address you provided. Please check your inbox and confirm the email to complete the registration process. +

+

+ If you don't find the email in your inbox, please also check your spam folder. +

+
+
+ } + + @if (registerForm && !showSuccess) { +
+
+ + + @if (f['playerName'].invalid && (f['playerName'].dirty || f['playerName'].touched)) { +
+ @if (f['playerName'].hasError('required')) { +

Player name is required

+ } +
+ } +
+ +
+ + + @if (f['email'].invalid && (f['email'].dirty || f['email'].touched)) { +
+ @if (f['email'].hasError('required')) { +

Email is required

+ } + @if (f['email'].hasError('email')) { +

Invalid email address

+ } +
+ } +
+ +
+
+ + + + +
+ + + + + +
+

Password is required

+
+
+ + Must contain a number +
+
+ + Minimum length required +
+
+ + Must contain a capital letter +
+
+ + Must contain a lowercase letter +
+
+ + Must contain special characters +
+
+
+
+ +
+ + + + +
+ +
+

Confirmation is required

+

Passwords do not match

+
+ +
+ +
+
+ } +
diff --git a/Ui/src/app/Authentication/register/register.component.ts b/Ui/src/app/Authentication/register/register.component.ts new file mode 100644 index 0000000..1dae4e3 --- /dev/null +++ b/Ui/src/app/Authentication/register/register.component.ts @@ -0,0 +1,84 @@ +import {Component, inject, OnInit} from '@angular/core'; +import {ActivatedRoute} from "@angular/router"; +import {ToastrService} from "ngx-toastr"; +import {AuthenticationService} from "../../services/authentication.service"; +import {FormControl, FormGroup, Validators} from "@angular/forms"; +import {PasswordValidators} from "../../helpers/passwordValidators"; +import {RegisterUserModel} from "../../models/registerUser.model"; +import {environment} from "../../../environments/environment"; + +@Component({ + selector: 'app-register', + templateUrl: './register.component.html', + styleUrl: './register.component.css' +}) +export class RegisterComponent implements OnInit { + + private readonly _activatedRoute: ActivatedRoute = inject(ActivatedRoute); + private readonly _toastr: ToastrService = inject(ToastrService); + private readonly _authenticationService: AuthenticationService = inject(AuthenticationService); + + public showError: boolean = false; + public showSuccess: boolean = false; + public registerForm: FormGroup | undefined; + isInputText: boolean = false; + playerName: string = ""; + + ngOnInit() { + + const role = this._activatedRoute.snapshot.queryParams['role']; + const allianceId = this._activatedRoute.snapshot.queryParams['allianceId']; + const email = this._activatedRoute.snapshot.queryParams['email']; + + if (!role || !email || !allianceId) { + this.showError = true; + return; + } + this.createRegisterForm(role, email, allianceId); + } + + get f() { + return this.registerForm!.controls; + } + + createRegisterForm(role: string, email: string, allianceId: string) { + this.registerForm = new FormGroup({ + email: new FormControl(email, [Validators.required, Validators.email]), + allianceId: new FormControl(allianceId, [Validators.required]), + roleId: new FormControl(role, [Validators.required]), + playerName: new FormControl('', [Validators.required]), + confirmPassword: new FormControl('', [Validators.required]), + password: new FormControl('', Validators.compose([ + Validators.required, + PasswordValidators.patternValidator(new RegExp("(?=.*[0-9])"), {hasNumber: true}), + PasswordValidators.patternValidator(new RegExp("(?=.*[A-Z])"), {hasCapitalCase: true}), + PasswordValidators.patternValidator(new RegExp("(?=.*[a-z])"), {hasSmallCase: true}), + PasswordValidators.patternValidator(new RegExp("(?=.*[$@^!%*?&+])"), {hasSpecialCharacters: true}), + Validators.minLength(8) + ])) + }, { + validators: PasswordValidators.passwordMatch('password', 'confirmPassword') + }); + } + + onSubmit() { + this.playerName = this.registerForm?.value.playerName; + + const registerUserModel: RegisterUserModel = this.registerForm!.value; + registerUserModel.emailConfirmUri = environment.emailConfirmUri; + + this._authenticationService.registerUser(registerUserModel).subscribe({ + next: ((response) => { + if (response) { + this.playerName = registerUserModel.playerName; + this._toastr.success('Registration successful', 'Register'); + this.showSuccess = true; + } + }), + error: ((error) => { + console.log(error); + this._toastr.error(error.error.name ?? 'Registration failed', 'Register'); + }) + }); + } +} diff --git a/Ui/src/app/Authentication/reset-password/reset-password.component.css b/Ui/src/app/Authentication/reset-password/reset-password.component.css new file mode 100644 index 0000000..9f8efe4 --- /dev/null +++ b/Ui/src/app/Authentication/reset-password/reset-password.component.css @@ -0,0 +1,31 @@ +/* Ensure the form takes a limited width on mobile */ +.form-container { + max-width: 100%; /* Default to full width */ + margin: 0 auto; /* Center on larger screens */ + padding: 10px; +} + +@media (min-width: 576px) { + .form-container { + max-width: 400px; /* Small devices (e.g., mobile phones) */ + } +} + +@media (min-width: 768px) { + .form-container { + max-width: 500px; /* Medium devices (e.g., tablets) */ + } +} + +@media (min-width: 992px) { + .form-container { + max-width: 700px; /* Larger devices (e.g., desktop) */ + } +} + +/* Add some space between form elements */ +.form-group { + margin-bottom: 15px; +} + + diff --git a/Ui/src/app/Authentication/reset-password/reset-password.component.html b/Ui/src/app/Authentication/reset-password/reset-password.component.html new file mode 100644 index 0000000..12f389c --- /dev/null +++ b/Ui/src/app/Authentication/reset-password/reset-password.component.html @@ -0,0 +1,107 @@ +
+

Reset Password

+ + @if (showError) { +
+
+ Reset Password Failed! +
+
+
An Error Occurred
+

+ Some required information is missing, or a technical issue has occurred. +

+

+ Please check the reset-password link or contact support if the issue persists. +

+
+
+ } + + @if (isSuccess) { +
+
+ Password Reset Successful! +
+
+
Your Password Has Been Reset
+

+ You can now log in with your new password. +

+

+ If you encounter any issues, please contact support. +

+ + +
+
+ } + + @if(resetPasswordForm && (!showError && !isSuccess)) { +
+
+ +
+ +
+ + + + +
+

Password is required

+
+
+ + Must have at least 1 number! +
+ +
+ + Must be at least 8 characters long! +
+
+ + Must contain at least 1 in capital letters! +
+
+ + Must contain at least 1 lowercase letter! +
+
+ + Must contain at least 1 special character! +
+
+
+
+
+ + +
+ +
+ + + + +
+

Repeat password is required

+

Passwords do not match

+
+
+
+ + +
+ +
+
+
+ } + +
diff --git a/Ui/src/app/Authentication/reset-password/reset-password.component.ts b/Ui/src/app/Authentication/reset-password/reset-password.component.ts new file mode 100644 index 0000000..4090cea --- /dev/null +++ b/Ui/src/app/Authentication/reset-password/reset-password.component.ts @@ -0,0 +1,87 @@ +import {Component, inject, OnInit} from '@angular/core'; +import {ActivatedRoute} from "@angular/router"; +import {ToastrService} from "ngx-toastr"; +import {AuthenticationService} from "../../services/authentication.service"; +import {FormControl, FormGroup, Validators} from "@angular/forms"; +import {PasswordValidators} from "../../helpers/passwordValidators"; +import {ResetPasswordModel} from "../../models/resetPassword.model"; + +@Component({ + selector: 'app-reset-password', + templateUrl: './reset-password.component.html', + styleUrl: './reset-password.component.css' +}) +export class ResetPasswordComponent implements OnInit { + + private readonly _activatedRoute: ActivatedRoute = inject(ActivatedRoute); + private readonly _toastr: ToastrService = inject(ToastrService); + private readonly _authenticationService: AuthenticationService = inject(AuthenticationService); + + private token: string | undefined; + private email: string | undefined; + + public showError: boolean = false; + public isSuccess: boolean = false; + public resetPasswordForm: FormGroup | undefined; + isInputText: boolean = false; + + get password() { + return this.resetPasswordForm!.get('password'); + } + + get confirmPassword() { + return this.resetPasswordForm!.get('confirmPassword'); + } + + ngOnInit() { + this.token = this._activatedRoute.snapshot.queryParams['token']; + this.email = this._activatedRoute.snapshot.queryParams['email']; + + if (!this.token || !this.email) { + this.showError = true; + return; + } + this.createResetPasswordForm(); + } + + createResetPasswordForm() { + this.resetPasswordForm = new FormGroup({ + confirmPassword: new FormControl('', [Validators.required]), + password: new FormControl('', Validators.compose([ + Validators.required, + PasswordValidators.patternValidator(new RegExp("(?=.*[0-9])"), {hasNumber: true}), + PasswordValidators.patternValidator(new RegExp("(?=.*[A-Z])"), {hasCapitalCase: true}), + PasswordValidators.patternValidator(new RegExp("(?=.*[a-z])"), {hasSmallCase: true}), + PasswordValidators.patternValidator(new RegExp("(?=.*[$@^!%*?&+#])"), {hasSpecialCharacters: true}), + Validators.minLength(8) + ])) + }, { + validators: PasswordValidators.passwordMatch('password', 'confirmPassword') + }); + } + + onSubmit() { + if (this.resetPasswordForm?.invalid) { + return; + } + + const resetPasswordModel: ResetPasswordModel = { + confirmPassword: this.confirmPassword?.value, + password: this.password?.value, + email: this.email!, + token: this.token!, + } + + this._authenticationService.resetPassword(resetPasswordModel).subscribe({ + next: ((response) => { + if (response) { + this.isSuccess = true; + } + }), + error: ((error) => { + console.log(error); + this._toastr.error('An error occurred. Please try again later.', 'Error'); + }) + }); + } +} diff --git a/Ui/src/app/Authentication/sign-up/sign-up.component.css b/Ui/src/app/Authentication/sign-up/sign-up.component.css new file mode 100644 index 0000000..e69de29 diff --git a/Ui/src/app/Authentication/sign-up/sign-up.component.html b/Ui/src/app/Authentication/sign-up/sign-up.component.html new file mode 100644 index 0000000..9f22875 --- /dev/null +++ b/Ui/src/app/Authentication/sign-up/sign-up.component.html @@ -0,0 +1,176 @@ +
+

Sign up

+ @if (!isSignUpSuccess) { +
+
+ + + @if (f['playerName'].invalid && (f['playerName'].dirty || f['playerName'].touched)) { +
+ @if (f['playerName'].hasError('required')) { +

Player name is required

+ } +
+ } +
+ +
+ + + @if (f['email'].invalid && (f['email'].dirty || f['email'].touched)) { +
+ @if (f['email'].hasError('required')) { +

Email is required

+ } + @if (f['email'].hasError('email')) { +

Invalid email format

+ } +
+ } +
+ +
+ + + @if (f['allianceServer'].invalid && (f['allianceServer'].dirty || f['allianceServer'].touched)) { +
+ @if (f['allianceServer'].hasError('required')) { +

Server is required

+ } +
+ } +
+ +
+ + + @if (f['allianceName'].invalid && (f['allianceName'].dirty || f['allianceName'].touched)) { +
+ @if (f['allianceName'].hasError('required')) { +

Alliance name is required

+ } +
+ } +
+ +
+ + + @if (f['allianceAbbreviation'].invalid && (f['allianceAbbreviation'].dirty || f['allianceAbbreviation'].touched)) { +
+ @if (f['allianceAbbreviation'].hasError('required')) { +

Alliance abbreviation is required

+ } +
+ } +
+ +
+
+ + + + +
+ + + + + +
+

Required

+
+
+ + Number +
+
+ + Min length +
+
+ + capital +
+
+ + lower case +
+
+ + special +
+
+
+
+ +
+ + + + +
+ +
+

Confirmation password is required

+

Passwords do not match

+
+ +
+ + +
+
+ } @else { +
+
+ Alliance Successfully Registered! +
+
+
Welcome, {{ playerName }}!
+

+ Your alliance {{ allianceName }} has been successfully registered. +

+

+ A confirmation email has been sent to the email address you provided. Please check your inbox and confirm the email to complete the registration. +

+

+ If you can't find the email in your inbox, please also check your spam folder. +

+
+
+ } +
+ diff --git a/Ui/src/app/Authentication/sign-up/sign-up.component.ts b/Ui/src/app/Authentication/sign-up/sign-up.component.ts new file mode 100644 index 0000000..4da1ab5 --- /dev/null +++ b/Ui/src/app/Authentication/sign-up/sign-up.component.ts @@ -0,0 +1,80 @@ +import {Component, inject} from '@angular/core'; +import {FormControl, FormGroup, Validators} from "@angular/forms"; +import {Router} from "@angular/router"; +import {PasswordValidators} from "../../helpers/passwordValidators"; +import {SignUpRequestModel} from "../../models/signUp.model"; +import {AuthenticationService} from "../../services/authentication.service"; +import {ToastrService} from "ngx-toastr"; +import {environment} from "../../../environments/environment"; + +@Component({ + selector: 'app-sign-up', + templateUrl: './sign-up.component.html', + styleUrl: './sign-up.component.css' +}) +export class SignUpComponent { + + private readonly _router: Router = inject(Router); + private readonly _authenticationService = inject(AuthenticationService); + private readonly _toastr: ToastrService = inject(ToastrService); + + public isSignUpSuccess: boolean = false; + public playerName: string = ''; + public allianceName: string = ''; + + isInputText: boolean = false; + + public signUpForm: FormGroup = new FormGroup({ + email: new FormControl('', [Validators.required, Validators.email]), + playerName: new FormControl('', [Validators.required]), + allianceServer: new FormControl(null, [Validators.required]), + allianceName: new FormControl('', [Validators.required]), + allianceAbbreviation: new FormControl('', [Validators.required, Validators.maxLength(5)]), + confirmPassword: new FormControl('', [Validators.required]), + password: new FormControl('', Validators.compose([ + Validators.required, + PasswordValidators.patternValidator(new RegExp("(?=.*[0-9])"), {hasNumber: true}), + PasswordValidators.patternValidator(new RegExp("(?=.*[A-Z])"), {hasCapitalCase: true}), + PasswordValidators.patternValidator(new RegExp("(?=.*[a-z])"), {hasSmallCase: true}), + PasswordValidators.patternValidator(new RegExp("(?=.*[$@^!%*?&+])"), {hasSpecialCharacters: true}), + Validators.minLength(8) + ])) + }, { + validators: PasswordValidators.passwordMatch('password', 'confirmPassword') + }); + + + get f() { + return this.signUpForm.controls; + } + + + onCancel() { + this._router.navigate(['login']).then(); + } + + onSignUp() { + if (this.signUpForm.invalid) { + return; + } + + const register: SignUpRequestModel = this.signUpForm.value as SignUpRequestModel; + register.emailConfirmUri = environment.emailConfirmUri; + + this._authenticationService.signUp(register).subscribe({ + next: ((response) => { + if (response) { + this.playerName = register.playerName; + this.allianceName = register.allianceName; + this._toastr.success('Successfully signed up', 'Sign Up'); + this.isSignUpSuccess = true; + } + }), + error: ((error) => { + console.log(error); + this._toastr.error(error.error.name ?? 'Sign up failed', 'Sign up'); + }) + }); + + } +} diff --git a/Ui/src/app/app-routing.module.ts b/Ui/src/app/app-routing.module.ts index 0297262..74d41eb 100644 --- a/Ui/src/app/app-routing.module.ts +++ b/Ui/src/app/app-routing.module.ts @@ -1,7 +1,46 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; +import {PlayerInformationComponent} from "./pages/player-information/player-information.component"; +import {PlayerComponent} from "./pages/player/player.component"; +import {DesertStormComponent} from "./pages/desert-storm/desert-storm.component"; +import {MarshalGuardComponent} from "./pages/marshal-guard/marshal-guard.component"; +import {VsDuelComponent} from "./pages/vs-duel/vs-duel.component"; +import {AllianceComponent} from "./pages/alliance/alliance.component"; +import {LoginComponent} from "./Authentication/login/login.component"; +import {authGuard} from "./guards/auth.guard"; +import {SignUpComponent} from "./Authentication/sign-up/sign-up.component"; +import {VsDuelDetailComponent} from "./pages/vs-duel/vs-duel-detail/vs-duel-detail.component"; +import {VsDuelEditComponent} from "./pages/vs-duel/vs-duel-edit/vs-duel-edit.component"; +import {MarshalGuardDetailComponent} from "./pages/marshal-guard/marshal-guard-detail/marshal-guard-detail.component"; +import {EmailConfirmationComponent} from "./Authentication/email-confirmation/email-confirmation.component"; +import {RegisterComponent} from "./Authentication/register/register.component"; +import {AccountComponent} from "./pages/account/account.component"; +import {ChangePasswordComponent} from "./pages/change-password/change-password.component"; +import {DesertStormDetailComponent} from "./pages/desert-storm/desert-storm-detail/desert-storm-detail.component"; +import {ResetPasswordComponent} from "./Authentication/reset-password/reset-password.component"; +import {CustomEventComponent} from "./pages/custom-event/custom-event.component"; -const routes: Routes = []; +const routes: Routes = [ + {path: 'players', component: PlayerComponent, canActivate: [authGuard]}, + {path: 'player-information/:id', component: PlayerInformationComponent, canActivate: [authGuard]}, + {path: 'marshal-guard', component: MarshalGuardComponent, canActivate: [authGuard]}, + {path: 'marshal-guard-detail/:id', component: MarshalGuardDetailComponent, canActivate: [authGuard]}, + {path: 'vs-duel', component: VsDuelComponent, canActivate: [authGuard]}, + {path: 'vs-duel-detail/:id', component: VsDuelDetailComponent, canActivate: [authGuard]}, + {path: 'vs-duel-edit/:id', component: VsDuelEditComponent, canActivate: [authGuard]}, + {path: 'desert-storm', component: DesertStormComponent, canActivate: [authGuard]}, + {path: 'desert-storm-detail/:id', component: DesertStormDetailComponent, canActivate: [authGuard]}, + { path: 'alliance', component: AllianceComponent, canActivate: [authGuard]}, + {path: 'account', component: AccountComponent, canActivate: [authGuard]}, + {path: 'change-password', component: ChangePasswordComponent, canActivate: [authGuard]}, + {path: 'custom-event', component: CustomEventComponent, canActivate: [authGuard]}, + {path: 'login', component: LoginComponent}, + {path: 'confirm-email', component: EmailConfirmationComponent}, + {path: 'sign-up', component: SignUpComponent}, + {path: 'register', component: RegisterComponent}, + {path: 'reset-password', component: ResetPasswordComponent}, + {path: '', redirectTo: 'players', pathMatch: 'full'}, +]; @NgModule({ imports: [RouterModule.forRoot(routes)], diff --git a/Ui/src/app/app.component.html b/Ui/src/app/app.component.html index 24ba6a6..ce9880c 100644 --- a/Ui/src/app/app.component.html +++ b/Ui/src/app/app.component.html @@ -1,3 +1,4 @@ -
- -
+ + +

loading . . .

+
diff --git a/Ui/src/app/app.component.ts b/Ui/src/app/app.component.ts index 209e439..576ccbf 100644 --- a/Ui/src/app/app.component.ts +++ b/Ui/src/app/app.component.ts @@ -1,10 +1,17 @@ -import { Component } from '@angular/core'; +import {Component, inject, OnInit} from '@angular/core'; +import {AuthenticationService} from "./services/authentication.service"; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrl: './app.component.css' }) -export class AppComponent { - title = 'Ui'; +export class AppComponent implements OnInit { + title = 'Last War - Player Management'; + + private readonly _authenticationService: AuthenticationService = inject(AuthenticationService); + + ngOnInit() { + this._authenticationService.autoLogin(); + } } diff --git a/Ui/src/app/app.module.ts b/Ui/src/app/app.module.ts index 5c20967..16e8248 100644 --- a/Ui/src/app/app.module.ts +++ b/Ui/src/app/app.module.ts @@ -4,17 +4,110 @@ import { BrowserModule } from '@angular/platform-browser'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { PlayerComponent } from './pages/player/player.component'; +import {provideHttpClient, withInterceptors} from "@angular/common/http"; +import {NgbModule, NgbRatingModule} from '@ng-bootstrap/ng-bootstrap'; +import {FormsModule, ReactiveFormsModule} from "@angular/forms"; +import {NgxPaginationModule} from "ngx-pagination"; +import { PlayerEditModalComponent } from './modals/player-edit-modal/player-edit-modal.component'; +import { DesertStormComponent } from './pages/desert-storm/desert-storm.component'; +import { MarshalGuardComponent } from './pages/marshal-guard/marshal-guard.component'; +import { MarshalGuardModalComponent } from './modals/marshal-guard-modal/marshal-guard-modal.component'; +import { PlayerInformationComponent } from './pages/player-information/player-information.component'; +import { NavigationComponent } from './navigation/navigation.component'; +import { VsDuelComponent } from './pages/vs-duel/vs-duel.component'; +import {NgxSpinnerModule} from "ngx-spinner"; +import {spinnerInterceptor} from "./interceptors/spinner.interceptor"; +import {ToastrModule} from "ngx-toastr"; +import { AllianceComponent } from './pages/alliance/alliance.component'; +import { LoginComponent } from './Authentication/login/login.component'; +import {JwtModule} from "@auth0/angular-jwt"; +import { SignUpComponent } from './Authentication/sign-up/sign-up.component'; +import {jwtInterceptor} from "./interceptors/jwt.interceptor"; +import { PlayerNoteModalComponent } from './modals/player-note-modal/player-note-modal.component'; +import { PlayerAdmonitionModalComponent } from './modals/player-admonition-modal/player-admonition-modal.component'; +import { PlayerInfoMarshalGuardComponent } from './pages/player-information/player-info-marshal-guard/player-info-marshal-guard.component'; +import { PlayerInfoVsDuelComponent } from './pages/player-information/player-info-vs-duel/player-info-vs-duel.component'; +import { WeekPipe } from './helpers/week.pipe'; +import { PlayerInfoDesertStormComponent } from './pages/player-information/player-info-desert-storm/player-info-desert-storm.component'; +import { PlayerInfoCustomEventComponent } from './pages/player-information/player-info-custom-event/player-info-custom-event.component'; +import { VsDuelCreateModalComponent } from './modals/vs-duel-create-modal/vs-duel-create-modal.component'; +import { VsDuelDetailComponent } from './pages/vs-duel/vs-duel-detail/vs-duel-detail.component'; +import { VsDuelEditComponent } from './pages/vs-duel/vs-duel-edit/vs-duel-edit.component'; +import { MarshalGuardDetailComponent } from './pages/marshal-guard/marshal-guard-detail/marshal-guard-detail.component'; +import { EmailConfirmationComponent } from './Authentication/email-confirmation/email-confirmation.component'; +import { InviteUserModalComponent } from './modals/invite-user-modal/invite-user-modal.component'; +import { RegisterComponent } from './Authentication/register/register.component'; +import { UserEditModalComponent } from './modals/user-edit-modal/user-edit-modal.component'; +import { AccountComponent } from './pages/account/account.component'; +import { ChangePasswordComponent } from './pages/change-password/change-password.component'; +import { DesertStormDetailComponent } from './pages/desert-storm/desert-storm-detail/desert-storm-detail.component'; +import { DesertStormParticipantsModalComponent } from './modals/desert-storm-participants-modal/desert-storm-participants-modal.component'; +import { ForgotPasswordComponent } from './Authentication/forgot-password/forgot-password.component'; +import { ResetPasswordComponent } from './Authentication/reset-password/reset-password.component'; +import { CustomEventComponent } from './pages/custom-event/custom-event.component'; +import { UnderDevelopmentComponent } from './helpers/under-development/under-development.component'; @NgModule({ declarations: [ - AppComponent + AppComponent, + PlayerComponent, + PlayerEditModalComponent, + DesertStormComponent, + MarshalGuardComponent, + MarshalGuardModalComponent, + PlayerInformationComponent, + NavigationComponent, + VsDuelComponent, + AllianceComponent, + LoginComponent, + SignUpComponent, + PlayerNoteModalComponent, + PlayerAdmonitionModalComponent, + PlayerInfoMarshalGuardComponent, + PlayerInfoVsDuelComponent, + WeekPipe, + PlayerInfoDesertStormComponent, + PlayerInfoCustomEventComponent, + VsDuelCreateModalComponent, + VsDuelDetailComponent, + VsDuelEditComponent, + MarshalGuardDetailComponent, + EmailConfirmationComponent, + InviteUserModalComponent, + RegisterComponent, + UserEditModalComponent, + AccountComponent, + ChangePasswordComponent, + DesertStormDetailComponent, + DesertStormParticipantsModalComponent, + ForgotPasswordComponent, + ResetPasswordComponent, + CustomEventComponent, + UnderDevelopmentComponent ], imports: [ BrowserModule, AppRoutingModule, - BrowserAnimationsModule + BrowserAnimationsModule, + NgbModule, + FormsModule, + NgxPaginationModule, + ReactiveFormsModule, + NgxSpinnerModule, + NgbRatingModule, + ToastrModule.forRoot({ + positionClass: 'toast-bottom-right', + }), + JwtModule.forRoot({ + config: { + tokenGetter: () => localStorage.getItem(''), + } + }) + ], + providers: [ + provideHttpClient(withInterceptors([spinnerInterceptor, jwtInterceptor])) ], - providers: [], bootstrap: [AppComponent] }) export class AppModule { } diff --git a/Ui/src/app/guards/auth.guard.ts b/Ui/src/app/guards/auth.guard.ts new file mode 100644 index 0000000..56a4e46 --- /dev/null +++ b/Ui/src/app/guards/auth.guard.ts @@ -0,0 +1,18 @@ +import {CanActivateFn, Router} from '@angular/router'; +import {AuthenticationService} from "../services/authentication.service"; +import {inject} from "@angular/core"; +import {LoggedInUser} from "../models/user.model"; + +export const authGuard: CanActivateFn = () => { + const authService: AuthenticationService = inject(AuthenticationService); + const router: Router = inject(Router); + + const loggedInUser: LoggedInUser | null = authService.user; + + if (loggedInUser) { + return true; + } else { + router.navigate(['login']).then(); + return false; + } +}; diff --git a/Ui/src/app/helpers/constants.ts b/Ui/src/app/helpers/constants.ts new file mode 100644 index 0000000..d29b57a --- /dev/null +++ b/Ui/src/app/helpers/constants.ts @@ -0,0 +1,3 @@ +export const LocalStorageKey = { + TOKEN: 'LastWarPlayerManagementToken', +} diff --git a/Ui/src/app/helpers/days.ts b/Ui/src/app/helpers/days.ts new file mode 100644 index 0000000..8052a4c --- /dev/null +++ b/Ui/src/app/helpers/days.ts @@ -0,0 +1,18 @@ +export interface Days { + name: string; + value: number; +} + +const days: Days[] = [ + { name: 'Monday', value: 1 }, + { name: 'Tuesday', value: 2 }, + { name: 'Wednesday', value: 3 }, + { name: 'Thursday', value: 4 }, + { name: 'Friday', value: 5 }, + { name: 'Saturday', value: 6 }, + { name: 'Sunday', value: 7 }, +]; + +export function getDays() { + return days; +} diff --git a/Ui/src/app/helpers/months.ts b/Ui/src/app/helpers/months.ts new file mode 100644 index 0000000..52c0e14 --- /dev/null +++ b/Ui/src/app/helpers/months.ts @@ -0,0 +1,23 @@ +const months: Month[] = [ + {name: 'January', value: 1}, + {name: 'February', value: 2}, + {name: 'March', value: 3}, + {name: 'April', value: 4}, + {name: 'May', value: 5}, + {name: 'June', value: 6}, + {name: 'July', value: 7}, + {name: 'August', value: 8}, + {name: 'September', value: 9}, + {name: 'October', value: 10}, + {name: 'November', value: 11}, + {name: 'December', value: 12}, +]; + +export function getMonths() { + return months; +} + +export interface Month { + name: string; + value: number; +} diff --git a/Ui/src/app/helpers/passwordValidators.ts b/Ui/src/app/helpers/passwordValidators.ts new file mode 100644 index 0000000..61d9621 --- /dev/null +++ b/Ui/src/app/helpers/passwordValidators.ts @@ -0,0 +1,49 @@ +import {AbstractControl, ValidationErrors, ValidatorFn} from "@angular/forms"; + +export class PasswordValidators { + // Validator function to check if password and confirmPassword match + static passwordMatch(password: string, confirmPassword: string): ValidatorFn { + return (formGroup: AbstractControl): { [key: string]: any } | null => { + const passwordControl: AbstractControl | null = formGroup.get(password); + const confirmPasswordControl: AbstractControl | null = formGroup.get(confirmPassword); + + // Check if controls are available + if (!passwordControl || !confirmPasswordControl) { + return null; + } + + // Check if there is an existing 'passwordMismatch' error + if ( + confirmPasswordControl.errors && + !confirmPasswordControl.errors["passwordMismatch"] + ) { + return null; + } + + // Check if password and confirmPassword values match + if (passwordControl.value !== confirmPasswordControl.value) { + confirmPasswordControl.setErrors({passwordMismatch: true}); + return {passwordMismatch: true}; + } else { + confirmPasswordControl.setErrors(null); + return null; + } + }; + } + + // Validator function to check if the control value matches a given regex pattern + static patternValidator(regex: RegExp, error: ValidationErrors): ValidatorFn { + return (control: AbstractControl): { [p: string]: any } | null => { + // If the control is empty, return no error + if (!control.value) { + return null; + } + + // Test the value of the control against the supplied regex + const valid = regex.test(control.value); + + // If true, return no error; otherwise, return the specified error + return valid ? null : error; + }; + } +} diff --git a/Ui/src/app/helpers/under-development/under-development.component.css b/Ui/src/app/helpers/under-development/under-development.component.css new file mode 100644 index 0000000..e69de29 diff --git a/Ui/src/app/helpers/under-development/under-development.component.html b/Ui/src/app/helpers/under-development/under-development.component.html new file mode 100644 index 0000000..e3616f8 --- /dev/null +++ b/Ui/src/app/helpers/under-development/under-development.component.html @@ -0,0 +1,14 @@ +
+
+ Feature Coming Soon! +
+
+
This Feature is Under Development
+

+ We're working hard to bring this feature to you. Stay tuned for updates! +

+

+ If you have any suggestions or feedback, feel free to contact our support team. +

+
+
diff --git a/Ui/src/app/helpers/under-development/under-development.component.ts b/Ui/src/app/helpers/under-development/under-development.component.ts new file mode 100644 index 0000000..11d019c --- /dev/null +++ b/Ui/src/app/helpers/under-development/under-development.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-under-development', + templateUrl: './under-development.component.html', + styleUrl: './under-development.component.css' +}) +export class UnderDevelopmentComponent { + +} diff --git a/Ui/src/app/helpers/week.pipe.ts b/Ui/src/app/helpers/week.pipe.ts new file mode 100644 index 0000000..396e470 --- /dev/null +++ b/Ui/src/app/helpers/week.pipe.ts @@ -0,0 +1,16 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'week' +}) +export class WeekPipe implements PipeTransform { + + transform(value: Date, ...args: unknown[]): unknown { + const d = new Date(value); + let yearStart = +new Date(d.getFullYear(), 0, 1); + let today = +new Date(d.getFullYear(),d.getMonth(),d.getDate()); + let dayOfYear = ((today - yearStart + 1) / 86400000); + return Math.ceil(dayOfYear / 7); + } + +} diff --git a/Ui/src/app/interceptors/jwt.interceptor.ts b/Ui/src/app/interceptors/jwt.interceptor.ts new file mode 100644 index 0000000..4263df9 --- /dev/null +++ b/Ui/src/app/interceptors/jwt.interceptor.ts @@ -0,0 +1,19 @@ +import { HttpInterceptorFn } from '@angular/common/http'; +import {JwtTokenService} from "../services/jwt-token.service"; +import {inject} from "@angular/core"; + +export const jwtInterceptor: HttpInterceptorFn = (req, next) => { + const jwtService: JwtTokenService = inject(JwtTokenService); + + const token: string | null = jwtService.getToken(); + + if (token) { + req = req.clone({ + setHeaders: { + Authorization: `Bearer ${token}` + } + }); + } + + return next(req); +}; diff --git a/Ui/src/app/interceptors/spinner.interceptor.ts b/Ui/src/app/interceptors/spinner.interceptor.ts new file mode 100644 index 0000000..b8016df --- /dev/null +++ b/Ui/src/app/interceptors/spinner.interceptor.ts @@ -0,0 +1,16 @@ +import { HttpInterceptorFn } from '@angular/common/http'; +import {SpinnerService} from "../services/spinner.service"; +import {inject} from "@angular/core"; +import {finalize} from "rxjs"; + +export const spinnerInterceptor: HttpInterceptorFn = (req, next) => { + const spinnerService: SpinnerService = inject(SpinnerService); + + spinnerService.busy(); + + return next(req).pipe( + finalize(() => { + spinnerService.idle() + }) + ); +}; diff --git a/Ui/src/app/modals/desert-storm-participants-modal/desert-storm-participants-modal.component.css b/Ui/src/app/modals/desert-storm-participants-modal/desert-storm-participants-modal.component.css new file mode 100644 index 0000000..0cc2638 --- /dev/null +++ b/Ui/src/app/modals/desert-storm-participants-modal/desert-storm-participants-modal.component.css @@ -0,0 +1,6 @@ +.text-color { + color: #43c315; +} +.text-small { + font-size: 0.8rem; +} diff --git a/Ui/src/app/modals/desert-storm-participants-modal/desert-storm-participants-modal.component.html b/Ui/src/app/modals/desert-storm-participants-modal/desert-storm-participants-modal.component.html new file mode 100644 index 0000000..72afd93 --- /dev/null +++ b/Ui/src/app/modals/desert-storm-participants-modal/desert-storm-participants-modal.component.html @@ -0,0 +1,39 @@ + + + + + + + diff --git a/Ui/src/app/modals/desert-storm-participants-modal/desert-storm-participants-modal.component.ts b/Ui/src/app/modals/desert-storm-participants-modal/desert-storm-participants-modal.component.ts new file mode 100644 index 0000000..33d78c6 --- /dev/null +++ b/Ui/src/app/modals/desert-storm-participants-modal/desert-storm-participants-modal.component.ts @@ -0,0 +1,56 @@ +import {Component, inject, Input, OnInit} from '@angular/core'; +import {PlayerService} from "../../services/player.service"; +import {NgbActiveModal} from "@ng-bootstrap/ng-bootstrap"; +import {PlayerModel} from "../../models/player.model"; + +@Component({ + selector: 'app-desert-storm-participants-modal', + templateUrl: './desert-storm-participants-modal.component.html', + styleUrl: './desert-storm-participants-modal.component.css' +}) +export class DesertStormParticipantsModalComponent implements OnInit { + + private readonly _playerService: PlayerService = inject(PlayerService); + + public activeModal: NgbActiveModal = inject(NgbActiveModal); + + public playerParticipated: {playerId: string, playerName: string, participated: boolean, registered: boolean, startPlayer: boolean}[] = []; + + @Input({required: true}) allianceId!: string; + @Input() players: {playerId: string, playerName: string, participated: boolean, registered: boolean, startPlayer: boolean}[] | undefined; + + ngOnInit() { + if (this.players) { + this.playerParticipated = [...this.players]; + } else { + this.getPlayers(); + } + } + + getPlayers() { + this._playerService.getAlliancePlayer(this.allianceId).subscribe({ + next: ((response) => { + if (response) { + response.forEach((player: PlayerModel) => { + this.playerParticipated.push({playerId: player.id, playerName: player.playerName, participated: false, registered: false, startPlayer: false}); + }); + this.playerParticipated.sort((a, b) => a.playerName.localeCompare(b.playerName)); + } + }) + }); + } + + onRegisterChange(player: { + playerId: string; + playerName: string; + participated: boolean; + registered: boolean; + startPlayer: boolean + }) { + player.registered = !player.registered; + if (!player.registered) { + player.participated = false; + player.startPlayer = false; + } + } +} diff --git a/Ui/src/app/modals/invite-user-modal/invite-user-modal.component.css b/Ui/src/app/modals/invite-user-modal/invite-user-modal.component.css new file mode 100644 index 0000000..e69de29 diff --git a/Ui/src/app/modals/invite-user-modal/invite-user-modal.component.html b/Ui/src/app/modals/invite-user-modal/invite-user-modal.component.html new file mode 100644 index 0000000..5cdafd4 --- /dev/null +++ b/Ui/src/app/modals/invite-user-modal/invite-user-modal.component.html @@ -0,0 +1,53 @@ +@if (inviteUserForm) { + + + + + + +} + + diff --git a/Ui/src/app/modals/invite-user-modal/invite-user-modal.component.ts b/Ui/src/app/modals/invite-user-modal/invite-user-modal.component.ts new file mode 100644 index 0000000..9664831 --- /dev/null +++ b/Ui/src/app/modals/invite-user-modal/invite-user-modal.component.ts @@ -0,0 +1,61 @@ +import {Component, inject, Input, OnInit} from '@angular/core'; +import {NgbActiveModal} from "@ng-bootstrap/ng-bootstrap"; +import {ToastrService} from "ngx-toastr"; +import {FormControl, FormGroup, Validators} from "@angular/forms"; +import {InviteUserModel} from "../../models/inviteUser.model"; +import {AuthenticationService} from "../../services/authentication.service"; +import {environment} from "../../../environments/environment"; + + +@Component({ + selector: 'app-invite-user-modal', + templateUrl: './invite-user-modal.component.html', + styleUrl: './invite-user-modal.component.css' +}) +export class InviteUserModalComponent implements OnInit { + + public activeModal: NgbActiveModal = inject(NgbActiveModal); + private readonly _toasterService: ToastrService = inject(ToastrService); + private readonly _authenticationService: AuthenticationService = inject(AuthenticationService); + + @Input({required: true}) userId!: string; + @Input({required: true}) allianceId!: string; + + inviteUserForm!: FormGroup; + public roles: string[] = ['Administrator', 'User', 'Guest']; + + get f() { + return this.inviteUserForm.controls; + } + + ngOnInit() { + this.inviteUserForm = new FormGroup({ + userId: new FormControl(this.userId, [Validators.required]), + allianceId: new FormControl(this.allianceId, [Validators.required]), + email: new FormControl('' , [Validators.required, Validators.email]), + role: new FormControl('', [Validators.required]), + }) + } + + onSubmit() { + if (this.inviteUserForm.invalid) { + return; + } + const inviteUserModel = this.inviteUserForm.value as InviteUserModel; + inviteUserModel.invitingUserId = this.userId; + inviteUserModel.registerUserUri = environment.registerUserUri; + + this._authenticationService.inviteUser(inviteUserModel).subscribe({ + next: (result) => { + if (result) { + this._toasterService.success('User successfully invite', 'Invite User'); + this.activeModal.close(); + } + }, + error: error => { + console.log(error); + this._toasterService.error('Error in invite user', 'Invite User'); + } + }); + } +} diff --git a/Ui/src/app/modals/marshal-guard-modal/marshal-guard-modal.component.css b/Ui/src/app/modals/marshal-guard-modal/marshal-guard-modal.component.css new file mode 100644 index 0000000..e1268fe --- /dev/null +++ b/Ui/src/app/modals/marshal-guard-modal/marshal-guard-modal.component.css @@ -0,0 +1,14 @@ +.check-box-container { + display: flex; + flex-wrap: wrap; + gap: 10px; +} + +.check-box-container .form-check { + flex: 1 1 calc(50% - 10px); + box-sizing: border-box; +} + +.text-color { + color: #43c315; +} diff --git a/Ui/src/app/modals/marshal-guard-modal/marshal-guard-modal.component.html b/Ui/src/app/modals/marshal-guard-modal/marshal-guard-modal.component.html new file mode 100644 index 0000000..4116f17 --- /dev/null +++ b/Ui/src/app/modals/marshal-guard-modal/marshal-guard-modal.component.html @@ -0,0 +1,25 @@ + + + + + + diff --git a/Ui/src/app/modals/marshal-guard-modal/marshal-guard-modal.component.ts b/Ui/src/app/modals/marshal-guard-modal/marshal-guard-modal.component.ts new file mode 100644 index 0000000..543ff3d --- /dev/null +++ b/Ui/src/app/modals/marshal-guard-modal/marshal-guard-modal.component.ts @@ -0,0 +1,42 @@ +import {Component, inject, Input, OnInit} from '@angular/core'; +import {NgbActiveModal} from "@ng-bootstrap/ng-bootstrap"; +import {PlayerService} from "../../services/player.service"; +import {PlayerModel} from "../../models/player.model"; + +@Component({ + selector: 'app-marshal-guard-modal', + templateUrl: './marshal-guard-modal.component.html', + styleUrl: './marshal-guard-modal.component.css' +}) +export class MarshalGuardModalComponent implements OnInit { + + private readonly _playerService: PlayerService = inject(PlayerService); + + public activeModal: NgbActiveModal = inject(NgbActiveModal); + public playerParticipated: {playerId: string, playerName: string, participated: boolean}[] = []; + + @Input({required: true}) allianceId!: string; + @Input() players: { playerId: string; playerName: string; participated: boolean; }[] | undefined; + + ngOnInit() { + if (this.players) { + this.playerParticipated = [...this.players]; + } else { + this.getPlayers(); + } + } + + getPlayers() { + this._playerService.getAlliancePlayer(this.allianceId).subscribe({ + next: ((response) => { + if (response) { + response.forEach((player: PlayerModel) => { + this.playerParticipated.push({playerId: player.id, playerName: player.playerName, participated: false}) + }); + this.playerParticipated.sort((a, b) => a.playerName.localeCompare(b.playerName)); + } + }) + }); + } + +} diff --git a/Ui/src/app/modals/player-admonition-modal/player-admonition-modal.component.css b/Ui/src/app/modals/player-admonition-modal/player-admonition-modal.component.css new file mode 100644 index 0000000..e69de29 diff --git a/Ui/src/app/modals/player-admonition-modal/player-admonition-modal.component.html b/Ui/src/app/modals/player-admonition-modal/player-admonition-modal.component.html new file mode 100644 index 0000000..0bc73b1 --- /dev/null +++ b/Ui/src/app/modals/player-admonition-modal/player-admonition-modal.component.html @@ -0,0 +1,81 @@ +@if (player) { + + + + +} + diff --git a/Ui/src/app/modals/player-admonition-modal/player-admonition-modal.component.ts b/Ui/src/app/modals/player-admonition-modal/player-admonition-modal.component.ts new file mode 100644 index 0000000..fa4eba5 --- /dev/null +++ b/Ui/src/app/modals/player-admonition-modal/player-admonition-modal.component.ts @@ -0,0 +1,155 @@ +import {Component, inject, Input, OnInit} from '@angular/core'; +import {PlayerModel} from "../../models/player.model"; +import {NgbActiveModal} from "@ng-bootstrap/ng-bootstrap"; +import {AdmonitionModel} from "../../models/admonition.model"; +import {FormControl, FormGroup, Validators} from "@angular/forms"; +import {ToastrService} from "ngx-toastr"; +import {AdmonitionService} from "../../services/admonition.service"; +import Swal from "sweetalert2"; + +@Component({ + selector: 'app-player-admonition-modal', + templateUrl: './player-admonition-modal.component.html', + styleUrl: './player-admonition-modal.component.css' +}) +export class PlayerAdmonitionModalComponent implements OnInit { + + private readonly _admonitionService: AdmonitionService = inject(AdmonitionService); + private readonly _toastr: ToastrService = inject(ToastrService); + + public activeModal: NgbActiveModal = inject(NgbActiveModal); + public isCreateMode: boolean = false; + public isEditMode: boolean = false; + public playerAdmonitions: AdmonitionModel[] = []; + public admonitionForm: FormGroup = new FormGroup({ + id: new FormControl(''), + playerId: new FormControl(''), + reason: new FormControl('', [Validators.required, Validators.maxLength(250)]), + }); + + + + @Input({required: true}) player!: PlayerModel; + + ngOnInit() { + this.getPlayerAdmonitions(this.player.id); + } + + getPlayerAdmonitions(playerId: string) { + this._admonitionService.getPlayerAdmonitions(playerId).subscribe({ + next: ((response) => { + if (response) { + this.playerAdmonitions = response; + } else { + this.playerAdmonitions = []; + } + }), + error: ((error) => { + console.log(error); + this._toastr.error('Could not load admonitions for player', 'Error loading admonitions'); + }) + }); + } + + + onInsertNewAdmonition() { + this.isCreateMode = true; + this.admonitionForm.patchValue({ + reason: '', + playerId: this.player.id, + }); + } + + onEditAdmonition(admonition: AdmonitionModel) { + this.isEditMode = true; + this.admonitionForm.patchValue({ + id: admonition.id, + reason: admonition.reason, + playerId: admonition.playerId + }); + } + + onDeleteAdmonition(admonition: AdmonitionModel) { + Swal.fire({ + title: "Delete Admonition ?", + text: 'Do you really want to delete this admonition ?', + icon: "warning", + showCancelButton: true, + confirmButtonColor: "#3085d6", + cancelButtonColor: "#d33", + confirmButtonText: "Yes, delete it!" + }).then((result) => { + if (result.isConfirmed) { + this._admonitionService.deleteAdmonition(admonition.id).subscribe({ + next: ((response) => { + if (response) { + Swal.fire({ + title: "Deleted!", + text: "Admonition has been deleted", + icon: "success" + }).then(_ => { + this.getPlayerAdmonitions(admonition.playerId); + }); + } + }), + error: (error: Error) => { + console.log(error); + this._toastr.error('Could not delete note', 'Error delete note'); + } + }); + } + }); + } + + onSubmit() { + if (this.admonitionForm.invalid) { + return; + } + const admonition: AdmonitionModel = this.admonitionForm.value as AdmonitionModel; + + if (this.isCreateMode) { + this.creatNewAdmonition(admonition); + } + if (this.isEditMode) { + this.updateAdmonition(admonition); + } + } + + onCancel() { + this.isCreateMode = false; + this.isEditMode = false; + this.admonitionForm.reset(); + } + + private creatNewAdmonition(admonition: AdmonitionModel) { + this._admonitionService.createAdmonition(admonition).subscribe({ + next: ((response) => { + if (response) { + this._toastr.success('Successfully created admonition', 'Created Admonition'); + this.getPlayerAdmonitions(admonition.playerId); + this.onCancel(); + } + }), + error: ((error) => { + console.log(error); + this._toastr.error('Could not create new Admonition', 'Error create admonition'); + }) + }); + } + + private updateAdmonition(admonition: AdmonitionModel) { + this._admonitionService.updateAdmonition(admonition.id, admonition).subscribe({ + next: ((response) => { + if (response) { + this._toastr.success('Successfully updated admonition', 'Update admonition'); + this.getPlayerAdmonitions(response.playerId); + this.onCancel(); + } + }), + error: ((error) => { + console.log(error); + this._toastr.error('Could not update Admonition', 'Error update admonition'); + }) + }); + } +} diff --git a/Ui/src/app/modals/player-edit-modal/player-edit-modal.component.css b/Ui/src/app/modals/player-edit-modal/player-edit-modal.component.css new file mode 100644 index 0000000..e69de29 diff --git a/Ui/src/app/modals/player-edit-modal/player-edit-modal.component.html b/Ui/src/app/modals/player-edit-modal/player-edit-modal.component.html new file mode 100644 index 0000000..233ba50 --- /dev/null +++ b/Ui/src/app/modals/player-edit-modal/player-edit-modal.component.html @@ -0,0 +1,66 @@ +@if (playerForm) { + + + + + + +} + diff --git a/Ui/src/app/modals/player-edit-modal/player-edit-modal.component.ts b/Ui/src/app/modals/player-edit-modal/player-edit-modal.component.ts new file mode 100644 index 0000000..cf4ce88 --- /dev/null +++ b/Ui/src/app/modals/player-edit-modal/player-edit-modal.component.ts @@ -0,0 +1,95 @@ +import {Component, inject, Input, OnInit} from '@angular/core'; +import {NgbActiveModal} from "@ng-bootstrap/ng-bootstrap"; +import {CreatePlayerModel, PlayerModel, UpdatePlayerModel} from "../../models/player.model"; +import {FormControl, FormGroup, Validators} from "@angular/forms"; +import {RankService} from "../../services/rank.service"; +import {RankModel} from "../../models/rank.model"; +import {PlayerService} from "../../services/player.service"; +import {ToastrService} from "ngx-toastr"; + +@Component({ + selector: 'app-player-edit-modal', + templateUrl: './player-edit-modal.component.html', + styleUrl: './player-edit-modal.component.css' +}) +export class PlayerEditModalComponent implements OnInit { + + public activeModal: NgbActiveModal = inject(NgbActiveModal); + private readonly _rankService: RankService = inject(RankService); + private readonly _playerService: PlayerService = inject(PlayerService); + private readonly _toasterService: ToastrService = inject(ToastrService); + + @Input({required: true}) currentPlayer!: PlayerModel; + @Input({required: true}) isUpdate!: boolean; + + playerForm!: FormGroup; + ranks: RankModel[] = []; + + get f() { + return this.playerForm.controls; + } + + ngOnInit() { + this.playerForm = new FormGroup({ + id: new FormControl(this.currentPlayer.id), + playerName: new FormControl(this.currentPlayer.playerName, [Validators.required, Validators.maxLength(250)]), + rankId: new FormControl(this.currentPlayer.rankId, [Validators.required]), + allianceId: new FormControl(this.currentPlayer.allianceId, [Validators.required]), + level: new FormControl(this.currentPlayer.level, [Validators.required]) + }); + this.getRanks(); + } + + getRanks() { + this._rankService.getRanks().subscribe({ + next: ((response: RankModel[]): void => { + if (response) { + this.ranks = response; + } + }) + }) + } + + onSubmit() { + if (this.playerForm.invalid) { + return; + } + if (this.isUpdate) { + const player: UpdatePlayerModel = this.playerForm.value as UpdatePlayerModel; + this.updatePlayer(player.id, player); + } else { + const player: CreatePlayerModel = this.playerForm.value as CreatePlayerModel; + this.insertPlayer(player); + } + } + + updatePlayer(playerId: string, player: UpdatePlayerModel) { + this._playerService.updatePlayer(playerId, player).subscribe({ + next: ((response) => { + if (response) { + this._toasterService.success('Player successfully updated!', 'Update Player'); + this.activeModal.close(response); + } + }), + error: ((error) => { + console.log(error); + this._toasterService.error('Player could not be updated', 'Error update Player') + }) + }); + } + + insertPlayer(player: CreatePlayerModel) { + this._playerService.insertPlayer(player).subscribe({ + next: ((response) => { + if (response) { + this._toasterService.success('Player successfully created!', 'Create Player'); + this.activeModal.close(response); + } + }), + error: ((error) => { + console.log(error); + this._toasterService.error('Player could not be created', 'Error create Player') + }) + }) + } +} diff --git a/Ui/src/app/modals/player-note-modal/player-note-modal.component.css b/Ui/src/app/modals/player-note-modal/player-note-modal.component.css new file mode 100644 index 0000000..e69de29 diff --git a/Ui/src/app/modals/player-note-modal/player-note-modal.component.html b/Ui/src/app/modals/player-note-modal/player-note-modal.component.html new file mode 100644 index 0000000..04353b9 --- /dev/null +++ b/Ui/src/app/modals/player-note-modal/player-note-modal.component.html @@ -0,0 +1,82 @@ +@if (player) { + + + + +} + + diff --git a/Ui/src/app/modals/player-note-modal/player-note-modal.component.ts b/Ui/src/app/modals/player-note-modal/player-note-modal.component.ts new file mode 100644 index 0000000..a9881f2 --- /dev/null +++ b/Ui/src/app/modals/player-note-modal/player-note-modal.component.ts @@ -0,0 +1,153 @@ +import {Component, inject, Input, OnInit} from '@angular/core'; +import {NgbActiveModal} from "@ng-bootstrap/ng-bootstrap"; +import {NoteModel} from "../../models/note.model"; +import {PlayerModel} from "../../models/player.model"; +import {NotesService} from "../../services/notes.service"; +import {ToastrService} from "ngx-toastr"; +import {FormControl, FormGroup, Validators} from "@angular/forms"; +import Swal from "sweetalert2"; + +@Component({ + selector: 'app-player-note-modal', + templateUrl: './player-note-modal.component.html', + styleUrl: './player-note-modal.component.css' +}) +export class PlayerNoteModalComponent implements OnInit { + + private readonly _noteService: NotesService = inject(NotesService); + private readonly _toastr: ToastrService = inject(ToastrService); + + public activeModal: NgbActiveModal = inject(NgbActiveModal); + public playerNotes: NoteModel[] = []; + public isCreateMode: boolean = false; + public isEditMode: boolean = false; + + public noteForm: FormGroup = new FormGroup({ + id: new FormControl(''), + playerNote: new FormControl('', [Validators.required, Validators.maxLength(500)]), + playerId: new FormControl(''), + }); + + @Input({required: true}) player!: PlayerModel; + + ngOnInit() { + this.getPlayerNotes(this.player.id); + } + + getPlayerNotes(playerId: string) { + this._noteService.getPlayerNotes(playerId).subscribe({ + next: ((response) => { + if (response) { + this.playerNotes = response; + } else { + this.playerNotes = []; + } + }), + error: ((error) => { + console.log(error); + this._toastr.error('Could not load notes for player', 'Error loading notes'); + }) + }); + } + + onInsertNewNote() { + this.isCreateMode = true; + this.noteForm.patchValue({ + playerNote: '', + playerId: this.player.id, + }); + } + + onSubmit() { + if (this.noteForm.invalid) { + return; + } + const note: NoteModel = this.noteForm.value as NoteModel; + + if (this.isCreateMode) { + this.createNewNote(note); + } + if (this.isEditMode) { + this.updateNote(note); + } + } + + onCancel() { + this.isCreateMode = false; + this.isEditMode = false; + this.noteForm.reset(); + } + + createNewNote(note: NoteModel) { + this._noteService.createNote(note).subscribe({ + next: ((response) => { + if (response) { + this._toastr.success('Successfully created note', 'Created note'); + this.getPlayerNotes(note.playerId); + this.onCancel(); + } + }), + error: ((error) => { + console.log(error); + this._toastr.error('Could not create new Note', 'Error create note'); + }) + }); + } + + updateNote(note: NoteModel) { + this._noteService.updateNote(note.id, note).subscribe({ + next: ((response) => { + if (response) { + this._toastr.success('Successfully updated note', 'Update note'); + this.getPlayerNotes(note.playerId); + this.onCancel(); + } + }), + error: ((error) => { + console.log(error); + this._toastr.error('Could not update Note', 'Error update note'); + }) + }); + } + + onEditNote(note: NoteModel) { + this.isEditMode = true; + this.noteForm.patchValue({ + id: note.id, + playerNote: note.playerNote, + playerId: note.playerId + }); + } + + deleteNote(note: NoteModel) { + Swal.fire({ + title: "Delete Note ?", + text: 'Do you really want to delete this note ?', + icon: "warning", + showCancelButton: true, + confirmButtonColor: "#3085d6", + cancelButtonColor: "#d33", + confirmButtonText: "Yes, delete it!" + }).then((result) => { + if (result.isConfirmed) { + this._noteService.deleteNote(note.id).subscribe({ + next: ((response) => { + if (response) { + Swal.fire({ + title: "Deleted!", + text: "Note has been deleted", + icon: "success" + }).then(_ => { + this.getPlayerNotes(note.playerId); + }); + } + }), + error: (error: Error) => { + console.log(error); + this._toastr.error('Could not delete note', 'Error delete note'); + } + }); + } + }); + } +} diff --git a/Ui/src/app/modals/user-edit-modal/user-edit-modal.component.css b/Ui/src/app/modals/user-edit-modal/user-edit-modal.component.css new file mode 100644 index 0000000..e69de29 diff --git a/Ui/src/app/modals/user-edit-modal/user-edit-modal.component.html b/Ui/src/app/modals/user-edit-modal/user-edit-modal.component.html new file mode 100644 index 0000000..400ee7f --- /dev/null +++ b/Ui/src/app/modals/user-edit-modal/user-edit-modal.component.html @@ -0,0 +1,68 @@ +@if (userForm) { + + + + + + +} diff --git a/Ui/src/app/modals/user-edit-modal/user-edit-modal.component.ts b/Ui/src/app/modals/user-edit-modal/user-edit-modal.component.ts new file mode 100644 index 0000000..0f2751f --- /dev/null +++ b/Ui/src/app/modals/user-edit-modal/user-edit-modal.component.ts @@ -0,0 +1,56 @@ +import {Component, inject, Input, OnInit} from '@angular/core'; +import {NgbActiveModal} from "@ng-bootstrap/ng-bootstrap"; +import {ToastrService} from "ngx-toastr"; +import {UserService} from "../../services/user.service"; +import {UpdateUserModel, UserModel} from "../../models/user.model"; +import {FormControl, FormGroup, Validators} from "@angular/forms"; + +@Component({ + selector: 'app-user-edit-modal', + templateUrl: './user-edit-modal.component.html', + styleUrl: './user-edit-modal.component.css' +}) +export class UserEditModalComponent implements OnInit { + + public activeModal: NgbActiveModal = inject(NgbActiveModal); + private readonly _userService: UserService = inject(UserService); + private readonly _toasterService: ToastrService = inject(ToastrService); + + @Input({required: true}) currentUser!: UserModel; + + userForm!: FormGroup; + public roles: string[] = ['Administrator', 'User', 'Guest']; + + get f() { + return this.userForm.controls; + } + + ngOnInit() { + this.userForm = new FormGroup({ + id: new FormControl(this.currentUser.id, [Validators.required]), + email: new FormControl({ value: this.currentUser.email, disabled: true }, Validators.required), + role: new FormControl(this.currentUser.role, [Validators.required]), + playerName: new FormControl(this.currentUser.playerName, [Validators.required]), + }); + } + + onSubmit() { + if (this.userForm.invalid) { + return; + } + + const updateUserModel: UpdateUserModel = this.userForm.value as UpdateUserModel; + this._userService.updateUser(updateUserModel.id, updateUserModel).subscribe({ + next: ((response) => { + if (response) { + this._toasterService.success('Update successfully', 'Update User'); + this.activeModal.close(response) + } + }), + error: (error) => { + console.log(error); + this._toasterService.error('Update Failed', 'Update User'); + } + }); + } +} diff --git a/Ui/src/app/modals/vs-duel-create-modal/vs-duel-create-modal.component.css b/Ui/src/app/modals/vs-duel-create-modal/vs-duel-create-modal.component.css new file mode 100644 index 0000000..e69de29 diff --git a/Ui/src/app/modals/vs-duel-create-modal/vs-duel-create-modal.component.html b/Ui/src/app/modals/vs-duel-create-modal/vs-duel-create-modal.component.html new file mode 100644 index 0000000..46c9785 --- /dev/null +++ b/Ui/src/app/modals/vs-duel-create-modal/vs-duel-create-modal.component.html @@ -0,0 +1,83 @@ +@if (vsDuelForm) { + + + + + + +} diff --git a/Ui/src/app/modals/vs-duel-create-modal/vs-duel-create-modal.component.ts b/Ui/src/app/modals/vs-duel-create-modal/vs-duel-create-modal.component.ts new file mode 100644 index 0000000..873ceef --- /dev/null +++ b/Ui/src/app/modals/vs-duel-create-modal/vs-duel-create-modal.component.ts @@ -0,0 +1,82 @@ +import {Component, inject, Input, OnInit} from '@angular/core'; +import {NgbActiveModal} from "@ng-bootstrap/ng-bootstrap"; +import {FormControl, FormGroup, Validators} from "@angular/forms"; +import {VsDuelModel} from "../../models/vsDuel.model"; +import {VsDuelService} from "../../services/vs-duel.service"; +import {ToastrService} from "ngx-toastr"; + +@Component({ + selector: 'app-vs-duel-edit-modal', + templateUrl: './vs-duel-create-modal.component.html', + styleUrl: './vs-duel-create-modal.component.css' +}) +export class VsDuelCreateModalComponent implements OnInit { + + private readonly _vsDuelService: VsDuelService = inject(VsDuelService); + private readonly _toastr: ToastrService = inject(ToastrService); + + public activeModal: NgbActiveModal = inject(NgbActiveModal); + public vsDuelForm!: FormGroup; + + @Input({required: true}) isUpdate!: boolean; + @Input({required: true}) vsDuelModel!: VsDuelModel; + + get f() { + return this.vsDuelForm.controls; + } + + ngOnInit() { + const d = new Date(this.vsDuelModel.eventDate); + this.vsDuelForm = new FormGroup({ + id: new FormControl(this.vsDuelModel.id), + allianceId: new FormControl(this.vsDuelModel.allianceId), + eventDate: new FormControl(new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate())).toISOString().substring(0, 10)), + won: new FormControl(this.vsDuelModel.won), + opponentName: new FormControl(this.vsDuelModel.opponentName, [Validators.required]), + opponentServer: new FormControl(this.vsDuelModel.opponentServer, [Validators.required]), + opponentPower: new FormControl(this.vsDuelModel.opponentPower, [Validators.required]), + opponentSize: new FormControl(this.vsDuelModel.opponentSize, [Validators.required]), + }); + } + + onSubmit() { + if (this.vsDuelForm.invalid) { + return; + } + + const vsDuel: VsDuelModel = this.vsDuelForm.value as VsDuelModel; + + this.isUpdate ? this.updateVsDuel(vsDuel) : this.createVsDuel(vsDuel); + } + + private createVsDuel(vsDuel: VsDuelModel) { + this._vsDuelService.createVsDuel(vsDuel).subscribe({ + next: ((response) => { + if (response) { + this._toastr.success('Successfully created VS-Duel', 'Successfully created'); + this.activeModal.close(response); + } + }), + error: ((error) => { + console.log(error); + this._toastr.error('Failed to create VS-Duel', 'Failed to create'); + }) + }); + } + + private updateVsDuel(vsDuel: VsDuelModel) { + this._vsDuelService.updateVsDuel(vsDuel.id, vsDuel).subscribe({ + next: ((response) => { + if (response) { + this._toastr.success('Successfully updated VS-Duel', 'Successfully updated'); + this.activeModal.close(response); + } + }), + error: ((error) => { + console.log(error); + this._toastr.error('Failed to update VS-Duel', 'Update failed'); + }) + }); + } + +} diff --git a/Ui/src/app/models/admonition.model.ts b/Ui/src/app/models/admonition.model.ts new file mode 100644 index 0000000..4419fba --- /dev/null +++ b/Ui/src/app/models/admonition.model.ts @@ -0,0 +1,9 @@ +export interface AdmonitionModel { + id: string; + reason: string; + playerId: string; + createdOn: Date; + createdBy: string; + modifiedOn?: Date; + modifiedBy?: string; +} diff --git a/Ui/src/app/models/alliance.model.ts b/Ui/src/app/models/alliance.model.ts new file mode 100644 index 0000000..2a577e6 --- /dev/null +++ b/Ui/src/app/models/alliance.model.ts @@ -0,0 +1,9 @@ +export interface AllianceModel { + id: string; + server: number; + name: string; + abbreviation: string; + createdOn: Date; + modifiedOn?: Date; + modifiedBy?: string; +} diff --git a/Ui/src/app/models/changePassword.model.ts b/Ui/src/app/models/changePassword.model.ts new file mode 100644 index 0000000..ab8c7ae --- /dev/null +++ b/Ui/src/app/models/changePassword.model.ts @@ -0,0 +1,6 @@ +export interface ChangePasswordModel { + userId: string; + currentPassword: string; + newPassword: string; + confirmPassword: string; +} diff --git a/Ui/src/app/models/confirmEmailRequest.model.ts b/Ui/src/app/models/confirmEmailRequest.model.ts new file mode 100644 index 0000000..3551c5e --- /dev/null +++ b/Ui/src/app/models/confirmEmailRequest.model.ts @@ -0,0 +1,4 @@ +export interface ConfirmEmailRequestModel { + email: string; + token: string; +} diff --git a/Ui/src/app/models/customEvent.model.ts b/Ui/src/app/models/customEvent.model.ts new file mode 100644 index 0000000..b1439e0 --- /dev/null +++ b/Ui/src/app/models/customEvent.model.ts @@ -0,0 +1,15 @@ +import {CustomEventParticipantModel} from "./customEventParticipant.model"; + +export interface CustomEventModel { + id: string; + allianceId: string; + name: string; + description: string; + isPointsEvent?: boolean; + isParticipationEvent?: boolean; + eventDate: Date; +} + +export interface CustomEventDetailModel extends CustomEventModel { + customEventParticipants: CustomEventParticipantModel[]; +} diff --git a/Ui/src/app/models/customEventParticipant.model.ts b/Ui/src/app/models/customEventParticipant.model.ts new file mode 100644 index 0000000..c0c3b41 --- /dev/null +++ b/Ui/src/app/models/customEventParticipant.model.ts @@ -0,0 +1,8 @@ +export interface CustomEventParticipantModel { + id: string; + playerId: string; + customEventId: string; + participated?: boolean; + achievedPoints?: number; + playerName: string; +} diff --git a/Ui/src/app/models/decodedToken.model.ts b/Ui/src/app/models/decodedToken.model.ts new file mode 100644 index 0000000..9fd589d --- /dev/null +++ b/Ui/src/app/models/decodedToken.model.ts @@ -0,0 +1,9 @@ +export interface DecodedTokenModel { + userId: string; + email: string; + allianceId: string; + playerName: string; + allianceName: string; + exp: number; + role: string; +} diff --git a/Ui/src/app/models/desertStorm.model.ts b/Ui/src/app/models/desertStorm.model.ts new file mode 100644 index 0000000..4ff1ab8 --- /dev/null +++ b/Ui/src/app/models/desertStorm.model.ts @@ -0,0 +1,28 @@ +import {DesertStormParticipantModel} from "./desertStormParticipant.model"; + +export interface DesertStormModel { + id: string; + allianceId: string; + createdBy: string; + modifiedOn?: Date; + modifiedBy?: string; + won: boolean; + opposingParticipants: number; + opponentServer: number; + eventDate: Date; + opponentName: string; + participants: number; +} + +export interface DesertStormDetailModel extends DesertStormModel { + desertStormParticipants: DesertStormParticipantModel[]; +} + +export interface CreateDesertStormModel { + allianceId: string; + won: boolean; + opposingParticipants: number; + opponentServer: number; + eventDate: string; + opponentName: string; +} diff --git a/Ui/src/app/models/desertStormParticipant.model.ts b/Ui/src/app/models/desertStormParticipant.model.ts new file mode 100644 index 0000000..b0878eb --- /dev/null +++ b/Ui/src/app/models/desertStormParticipant.model.ts @@ -0,0 +1,17 @@ +export interface DesertStormParticipantModel { + id: string; + desertStormId: string; + playerId: string; + playerName: string; + registered: boolean; + participated: boolean; + startPlayer: boolean; +} + +export interface CreateDesertStormParticipantModel { + desertStormId: string; + playerId: string; + registered: boolean; + participated: boolean; + startPlayer: boolean; +} diff --git a/Ui/src/app/models/emailConfirmationRequest.model.ts b/Ui/src/app/models/emailConfirmationRequest.model.ts new file mode 100644 index 0000000..8eca60b --- /dev/null +++ b/Ui/src/app/models/emailConfirmationRequest.model.ts @@ -0,0 +1,4 @@ +export interface EmailConfirmationRequestModel { + email: string; + clientUri: string; +} diff --git a/Ui/src/app/models/forgotPassword.model.ts b/Ui/src/app/models/forgotPassword.model.ts new file mode 100644 index 0000000..df18b91 --- /dev/null +++ b/Ui/src/app/models/forgotPassword.model.ts @@ -0,0 +1,4 @@ +export interface ForgotPasswordModel { + email: string; + resetPasswordUri: string; +} diff --git a/Ui/src/app/models/inviteUser.model.ts b/Ui/src/app/models/inviteUser.model.ts new file mode 100644 index 0000000..c1813b4 --- /dev/null +++ b/Ui/src/app/models/inviteUser.model.ts @@ -0,0 +1,7 @@ +export interface InviteUserModel { + email: string; + invitingUserId: string; + allianceId: string; + role: string; + registerUserUri: string +} diff --git a/Ui/src/app/models/login.model.ts b/Ui/src/app/models/login.model.ts new file mode 100644 index 0000000..05c2f13 --- /dev/null +++ b/Ui/src/app/models/login.model.ts @@ -0,0 +1,8 @@ +export interface LoginRequestModel { + email: string; + password: string; +} + +export interface LoginResponseModel { + token: string; +} diff --git a/Ui/src/app/models/marshalGuard.model.ts b/Ui/src/app/models/marshalGuard.model.ts new file mode 100644 index 0000000..55d2f44 --- /dev/null +++ b/Ui/src/app/models/marshalGuard.model.ts @@ -0,0 +1,35 @@ +import {MarshalGuardParticipantModel} from "./marshalGuardParticipant.model"; + +export interface MarshalGuardModel { + id: string; + allianceId: string; + participants: number; + level: number; + rewardPhase: number; + allianceSize: number; + eventDate: Date; + createdBy: string; + modifiedOn?: Date; + modifiedBy?: string; +} + +export interface MarshalGuardDetailModel extends MarshalGuardModel { + marshalGuardParticipants: MarshalGuardParticipantModel[]; +} + +export interface CreateMarshalGuardModel { + allianceId: string; + rewardPhase: number; + level: number; + allianceSize: number; + eventDate: string; +} + +export interface UpdateMarshalGuardModel { + id: string; + allianceId: string; + rewardPhase: number; + level: number; + eventDate: string; + participants: number; +} diff --git a/Ui/src/app/models/marshalGuardParticipant.model.ts b/Ui/src/app/models/marshalGuardParticipant.model.ts new file mode 100644 index 0000000..5f1d3c8 --- /dev/null +++ b/Ui/src/app/models/marshalGuardParticipant.model.ts @@ -0,0 +1,13 @@ +export interface MarshalGuardParticipantModel { + id: string; + playerId: string; + marshalGuardId: string; + participated: boolean; + playerName: string; +} + +export interface CreateMarshalGuardParticipantModel { + playerId: string; + marshalGuardId: string; + participated: boolean; +} diff --git a/Ui/src/app/models/note.model.ts b/Ui/src/app/models/note.model.ts new file mode 100644 index 0000000..c06c4ba --- /dev/null +++ b/Ui/src/app/models/note.model.ts @@ -0,0 +1,9 @@ +export interface NoteModel { + id: string; + playerId: string; + playerNote: string; + createdOn: Date; + createdBy: string; + modifiedOn?: Date; + modifiedBy?:string; +} diff --git a/Ui/src/app/models/player.model.ts b/Ui/src/app/models/player.model.ts new file mode 100644 index 0000000..1419af6 --- /dev/null +++ b/Ui/src/app/models/player.model.ts @@ -0,0 +1,28 @@ +export interface PlayerModel { + id: string; + playerName: string; + level: number; + rankName: string; + rankId: string; + allianceId: string; + createdOn: Date; + createdBy: string; + modifiedOn?: Date; + modifiedBy?: string; + notesCount: number; + admonitionsCount: number; +} + +export interface CreatePlayerModel { + playerName: string; + rankId: string; + allianceId: string; + level: number; +} + +export interface UpdatePlayerModel { + id: string; + playerName: string; + rankId: string; + level: number; +} diff --git a/Ui/src/app/models/rank.model.ts b/Ui/src/app/models/rank.model.ts new file mode 100644 index 0000000..2b5e201 --- /dev/null +++ b/Ui/src/app/models/rank.model.ts @@ -0,0 +1,4 @@ +export interface RankModel { + id: string; + name: string; +} diff --git a/Ui/src/app/models/registerUser.model.ts b/Ui/src/app/models/registerUser.model.ts new file mode 100644 index 0000000..107c9dd --- /dev/null +++ b/Ui/src/app/models/registerUser.model.ts @@ -0,0 +1,8 @@ +export interface RegisterUserModel { + email: string; + password: string; + playerName: string; + allianceId: string; + roleId: string; + emailConfirmUri: string; +} diff --git a/Ui/src/app/models/resetPassword.model.ts b/Ui/src/app/models/resetPassword.model.ts new file mode 100644 index 0000000..79a2511 --- /dev/null +++ b/Ui/src/app/models/resetPassword.model.ts @@ -0,0 +1,6 @@ +export interface ResetPasswordModel { + email: string; + password: string; + confirmPassword: string; + token: string; +} diff --git a/Ui/src/app/models/signUp.model.ts b/Ui/src/app/models/signUp.model.ts new file mode 100644 index 0000000..b2030d2 --- /dev/null +++ b/Ui/src/app/models/signUp.model.ts @@ -0,0 +1,9 @@ +export interface SignUpRequestModel { + email: string; + playerName: string; + password: string; + allianceServer: number; + allianceName: string; + allianceAbbreviation: string; + emailConfirmUri: string +} diff --git a/Ui/src/app/models/user.model.ts b/Ui/src/app/models/user.model.ts new file mode 100644 index 0000000..1d7855d --- /dev/null +++ b/Ui/src/app/models/user.model.ts @@ -0,0 +1,20 @@ +export interface UserModel { + id: string; + playerName: string; + email: string; + role: string; +} + +export interface LoggedInUser { + id: string; + allianceId: string; + email: string; + userName: string; + allianceName: string; +} + +export interface UpdateUserModel { + id: string; + playerName: string; + role: string; +} diff --git a/Ui/src/app/models/vsDuel.model.ts b/Ui/src/app/models/vsDuel.model.ts new file mode 100644 index 0000000..2657d29 --- /dev/null +++ b/Ui/src/app/models/vsDuel.model.ts @@ -0,0 +1,19 @@ +import {VsDuelParticipantModel} from "./vsDuelParticipant.model"; + +export interface VsDuelModel { + id: string; + allianceId: string; + eventDate: Date; + won: boolean; + opponentName: string; + opponentServer: number; + opponentPower: number; + opponentSize: number; + createdBy: string; + modifiedOn?: Date; + modifiedBy?: string; +} + +export interface VsDuelDetailModel extends VsDuelModel { + vsDuelParticipants: VsDuelParticipantModel[]; +} diff --git a/Ui/src/app/models/vsDuelParticipant.model.ts b/Ui/src/app/models/vsDuelParticipant.model.ts new file mode 100644 index 0000000..35aeb0e --- /dev/null +++ b/Ui/src/app/models/vsDuelParticipant.model.ts @@ -0,0 +1,7 @@ +export interface VsDuelParticipantModel { + id: string; + playerId: string; + vsDuelId: string; + weeklyPoints: number; + playerName: string; +} diff --git a/Ui/src/app/navigation/navigation.component.css b/Ui/src/app/navigation/navigation.component.css new file mode 100644 index 0000000..f50afaa --- /dev/null +++ b/Ui/src/app/navigation/navigation.component.css @@ -0,0 +1,9 @@ +.version { + font-size: 10px; + color: dodgerblue; + cursor: pointer; +} + +.user-menu { + cursor: pointer; +} diff --git a/Ui/src/app/navigation/navigation.component.html b/Ui/src/app/navigation/navigation.component.html new file mode 100644 index 0000000..9aeb1dc --- /dev/null +++ b/Ui/src/app/navigation/navigation.component.html @@ -0,0 +1,86 @@ +@if (loggedInUser) { + +} + + + + diff --git a/Ui/src/app/navigation/navigation.component.ts b/Ui/src/app/navigation/navigation.component.ts new file mode 100644 index 0000000..32ab4a7 --- /dev/null +++ b/Ui/src/app/navigation/navigation.component.ts @@ -0,0 +1,44 @@ +import {Component, inject, OnDestroy, OnInit} from '@angular/core'; +import {AuthenticationService} from "../services/authentication.service"; +import {LoggedInUser} from "../models/user.model"; +import {Subscription} from "rxjs"; +import {environment} from "../../environments/environment"; + +@Component({ + selector: 'app-navigation', + templateUrl: './navigation.component.html', + styleUrl: './navigation.component.css' +}) +export class NavigationComponent implements OnInit, OnDestroy { + + private readonly _authenticationService: AuthenticationService = inject(AuthenticationService); + + private _authStateChange$: Subscription | undefined; + + isShown: boolean = false; + version: string = environment.version; + loggedInUser: LoggedInUser | null = null; + + ngOnInit() { + this._authStateChange$ = this._authenticationService.authStateChange.subscribe({ + next: ((response) => { + if (response) { + this.loggedInUser = response; + } else { + this.loggedInUser = null; + } + }) + }); + } + + + onLogout() { + this._authenticationService.logout(); + } + + ngOnDestroy() { + if (this._authStateChange$) { + this._authStateChange$.unsubscribe(); + } + } +} diff --git a/Ui/src/app/pages/account/account.component.css b/Ui/src/app/pages/account/account.component.css new file mode 100644 index 0000000..e69de29 diff --git a/Ui/src/app/pages/account/account.component.html b/Ui/src/app/pages/account/account.component.html new file mode 100644 index 0000000..da09604 --- /dev/null +++ b/Ui/src/app/pages/account/account.component.html @@ -0,0 +1,44 @@ +
+

Account

+ + @if (userForm) { +
+
+ + + @if (f['playerName'].invalid && (f['playerName'].dirty || f['playerName'].touched)) { +
+ @if (f['playerName'].hasError('required')) { +

Player name is required

+ } + @if (f['playerName'].hasError('maxlength')) { +

Maximum 250 characters allowed

+ } +
+ } +
+
+ + +
+
+ + +
+ @if (isEditMode) { +
+ + +
+ } @else { +
+ + +
+ } +
+ } +
diff --git a/Ui/src/app/pages/account/account.component.ts b/Ui/src/app/pages/account/account.component.ts new file mode 100644 index 0000000..4bc707b --- /dev/null +++ b/Ui/src/app/pages/account/account.component.ts @@ -0,0 +1,94 @@ +import {Component, inject, OnInit} from '@angular/core'; +import {JwtTokenService} from "../../services/jwt-token.service"; +import {UserModel} from "../../models/user.model"; +import {FormControl, FormGroup, Validators} from "@angular/forms"; +import {UserService} from "../../services/user.service"; +import {ToastrService} from "ngx-toastr"; +import {HttpErrorResponse} from "@angular/common/http"; +import Swal from "sweetalert2"; +import {AuthenticationService} from "../../services/authentication.service"; + +@Component({ + selector: 'app-account', + templateUrl: './account.component.html', + styleUrl: './account.component.css' +}) +export class AccountComponent implements OnInit{ + + private readonly _tokenService: JwtTokenService = inject(JwtTokenService); + private readonly _userService: UserService = inject(UserService); + private readonly _toastr: ToastrService = inject(ToastrService); + private readonly _authenticationService: AuthenticationService = inject(AuthenticationService); + + public userForm: FormGroup | undefined; + public isEditMode: boolean = false; + public currentUser!: UserModel; + + get f() { + return this.userForm!.controls; + } + + ngOnInit() { + const userId = this._tokenService.getUserId(); + + if (!userId) { + return; + } + + this._userService.getUser(userId).subscribe({ + next: (response: UserModel) => { + if (response) { + this.createUserForm(response); + this.currentUser = response; + } + }, + error: (error: HttpErrorResponse) => { + console.log(error); + this._toastr.error('Error load user', 'Load users'); + } + }); + } + + createUserForm(user: UserModel) { + this.userForm = new FormGroup({ + id: new FormControl(user.id), + email: new FormControl({value: user.email, disabled: true}), + role: new FormControl({value: user.role, disabled: true}), + playerName: new FormControl(user.playerName, [Validators.required]), + }); + this.userForm.disable(); + } + + onEdit() { + this.userForm!.controls['playerName'].enable(); + this.isEditMode = true; + } + + onCancel() { + this.userForm!.reset(); + this.createUserForm(this.currentUser); + this.isEditMode = false; + this.userForm!.disable(); + } + + onSubmit() { + if (this.userForm!.invalid) { + return; + } + + const user = this.userForm!.getRawValue(); + this._userService.updateUser(user.id, user).subscribe({ + next: ((response: UserModel) => { + if (response) { + this._toastr.success("User updated successfully.", 'Update user'); + Swal.fire('Auto logged out', 'You have to log in again and will be logged out automatically', 'warning') + .then(() => this._authenticationService.logout()); + } + }), + error: ((error: HttpErrorResponse) => { + console.log(error); + this._toastr.error("Error updating user", 'Update user'); + }) + }) + } +} diff --git a/Ui/src/app/pages/alliance/alliance.component.css b/Ui/src/app/pages/alliance/alliance.component.css new file mode 100644 index 0000000..e69de29 diff --git a/Ui/src/app/pages/alliance/alliance.component.html b/Ui/src/app/pages/alliance/alliance.component.html new file mode 100644 index 0000000..d536476 --- /dev/null +++ b/Ui/src/app/pages/alliance/alliance.component.html @@ -0,0 +1,118 @@ +
+

Alliance

+ + +
+ +
diff --git a/Ui/src/app/pages/alliance/alliance.component.ts b/Ui/src/app/pages/alliance/alliance.component.ts new file mode 100644 index 0000000..53b8089 --- /dev/null +++ b/Ui/src/app/pages/alliance/alliance.component.ts @@ -0,0 +1,162 @@ +import {Component, inject, OnInit} from '@angular/core'; +import {AllianceService} from "../../services/alliance.service"; +import {AllianceModel} from "../../models/alliance.model"; +import {ToastrService} from "ngx-toastr"; +import {FormControl, FormGroup, Validators} from "@angular/forms"; +import {JwtTokenService} from "../../services/jwt-token.service"; +import {UserModel} from "../../models/user.model"; +import {NgbModal} from "@ng-bootstrap/ng-bootstrap"; +import {InviteUserModalComponent} from "../../modals/invite-user-modal/invite-user-modal.component"; +import {UserService} from "../../services/user.service"; +import {UserEditModalComponent} from "../../modals/user-edit-modal/user-edit-modal.component"; +import Swal from "sweetalert2"; + +@Component({ + selector: 'app-alliance', + templateUrl: './alliance.component.html', + styleUrl: './alliance.component.css' +}) +export class AllianceComponent implements OnInit { + + + private readonly _allianceService: AllianceService = inject(AllianceService); + private readonly _toastr: ToastrService = inject(ToastrService); + private readonly _tokenService: JwtTokenService = inject(JwtTokenService); + private readonly _modalService : NgbModal = inject(NgbModal); + private readonly _userService: UserService = inject(UserService); + + private allianceId = this._tokenService.getAllianceId(); + + public allianceForm: FormGroup | undefined; + public currentAlliance: AllianceModel | undefined; + active: number = 1; + public users: UserModel[] = []; + page: number = 1; + + get f() { + return this.allianceForm!.controls; + } + + ngOnInit() { + this.getAlliance(this.allianceId!); + this.getAllianceUsers(this.allianceId!); + } + + getAlliance(allianceId: string) { + this._allianceService.getAlliance(allianceId).subscribe({ + next: ((response) => { + if (response) { + this.currentAlliance = response; + this.createAllianceForm(response); + } + }), + error: ((error) => { + console.log(error); + this._toastr.error('Could not load alliance', 'Error load alliance'); + }) + }); + } + + getAllianceUsers(allianceId: string) { + this._userService.getAllianceUsers(allianceId).subscribe({ + next: ((response) => { + if (response) { + this.users = response; + } else { + this.users = []; + } + }), + error: ((error) => { + console.log(error); + this._toastr.error('Could not load alliance users', 'Error load users'); + }) + }) + } + + createAllianceForm(alliance: AllianceModel) { + this.allianceForm = new FormGroup({ + id: new FormControl(alliance.id), + server: new FormControl(alliance.server, [Validators.required]), + name: new FormControl(alliance.name, [Validators.required, Validators.maxLength(200)]), + abbreviation: new FormControl(alliance.abbreviation, [Validators.required, Validators.maxLength(4)]), + }); + } + + onSubmit() { + if (this.allianceForm?.invalid) { + return; + } + + const alliance: AllianceModel = this.allianceForm?.value; + + this._allianceService.updateAlliance(alliance.id, alliance).subscribe({ + next: ((response) => { + if (response) { + this._toastr.success('Successfully updated alliance', 'Update'); + this.currentAlliance = response; + this.createAllianceForm(response); + } + }), + error: ((error) => { + console.log(error); + this._toastr.error('Failed to update alliance', 'Update failed'); + this.getAlliance(alliance.id); + }) + }); + } + + onEditUser(user: UserModel) { + const modalRef = this._modalService.open(UserEditModalComponent, + {animation: true, backdrop: 'static', centered: true, size: 'lg'}); + modalRef.componentInstance.currentUser = user; + modalRef.closed.subscribe({ + next: ((response: UserModel) => { + if (response) { + this.getAllianceUsers(this.allianceId!); + } + }) + }) + } + + onDeleteUser(user: UserModel) { + Swal.fire({ + title: "Delete User ?", + text: `Do you really want to delete the user ${user.playerName}`, + icon: "warning", + showCancelButton: true, + confirmButtonColor: "#3085d6", + cancelButtonColor: "#d33", + confirmButtonText: "Yes, delete it!" + }).then((result) => { + if (result.isConfirmed) { + this._userService.deleteUser(user.id).subscribe({ + next: ((response) => { + if (response) { + Swal.fire({ + title: "Deleted!", + text: "User has been deleted", + icon: "success" + }).then(_ => this.getAllianceUsers(this.allianceId!)); + } + }), + error: (error: Error) => { + console.log(error); + } + }); + } + }); + } + + onInviteUser() { + const modalRef = this._modalService.open(InviteUserModalComponent, + {animation: true, backdrop: 'static', centered: true, size: 'lg'}); + modalRef.componentInstance.userId = this._tokenService.getUserId(); + modalRef.componentInstance.allianceId = this.allianceId; + modalRef.closed.subscribe({ + next: ((response) => { + if (response) { + } + }) + }) + } +} diff --git a/Ui/src/app/pages/change-password/change-password.component.css b/Ui/src/app/pages/change-password/change-password.component.css new file mode 100644 index 0000000..e69de29 diff --git a/Ui/src/app/pages/change-password/change-password.component.html b/Ui/src/app/pages/change-password/change-password.component.html new file mode 100644 index 0000000..0217750 --- /dev/null +++ b/Ui/src/app/pages/change-password/change-password.component.html @@ -0,0 +1,129 @@ +
+

Change password

+ + @if (changePasswordForm) { +
+ + +
+
+ + +
+ + + + + + @if (f['currentPassword'].invalid && (f['currentPassword'].dirty || f['currentPassword'].touched )) { +
+ @if (f['currentPassword'].hasError('required')) { +

Password is required

+ } +
+ } +
+ + +
+
+ + +
+ + + + + + @if (f['newPassword'].invalid && (f['newPassword'].dirty || f['newPassword'].touched )) { +
+ @if (f['newPassword'].hasError('required')) { +

Password is required

+ } + @if (!f['newPassword'].hasError('required')) { +
+
+ + Must have at least 1 number! +
+ +
+ + Must be at least 8 characters long! +
+
+ + Must contain at least 1 in capital letters! +
+
+ + Must contain at least 1 lowercase letter! +
+
+ + Must contain at least 1 special character! +
+
+ } +
+ } + +
+ + +
+
+ + +
+ + + + + + @if (f['confirmPassword'].invalid && (f['confirmPassword'].dirty || f['confirmPassword'].touched )) { +
+ @if (f['confirmPassword'].hasError('required')) { +

Repeat password is required

+ } + @if (f['confirmPassword'].hasError('passwordMismatch')) { +

Passwords do not match

+ } +
+ } +
+ + +
+ + +
+
+ } + +
diff --git a/Ui/src/app/pages/change-password/change-password.component.ts b/Ui/src/app/pages/change-password/change-password.component.ts new file mode 100644 index 0000000..c06112e --- /dev/null +++ b/Ui/src/app/pages/change-password/change-password.component.ts @@ -0,0 +1,64 @@ +import {Component, inject} from '@angular/core'; +import {FormControl, FormGroup, Validators} from "@angular/forms"; +import {PasswordValidators} from "../../helpers/passwordValidators"; +import {AuthenticationService} from "../../services/authentication.service"; +import {UserService} from "../../services/user.service"; +import {ToastrService} from "ngx-toastr"; +import Swal from "sweetalert2"; +import {HttpErrorResponse} from "@angular/common/http"; +import {JwtTokenService} from "../../services/jwt-token.service"; + +@Component({ + selector: 'app-change-password', + templateUrl: './change-password.component.html', + styleUrl: './change-password.component.css' +}) +export class ChangePasswordComponent { + + public isPasswordType: boolean = true; + private readonly _authenticationService: AuthenticationService = inject(AuthenticationService); + private readonly _userService: UserService = inject(UserService); + private readonly _toastr: ToastrService = inject(ToastrService); + private readonly _tokenService: JwtTokenService = inject(JwtTokenService); + + public changePasswordForm: FormGroup = new FormGroup({ + userId: new FormControl(this._tokenService.getUserId()!), + currentPassword: new FormControl('', [Validators.required]), + confirmPassword: new FormControl('', [Validators.required]), + newPassword: new FormControl('', Validators.compose([ + Validators.required, + PasswordValidators.patternValidator(RegExp("(?=.*[0-9])"), {hasNumber: true}), + PasswordValidators.patternValidator(RegExp("(?=.*[A-Z])"), {hasCapitalCase: true}), + PasswordValidators.patternValidator(RegExp("(?=.*[a-z])"), {hasSmallCase: true}), + PasswordValidators.patternValidator(RegExp("(?=.*[$@^!%*?&+#])"), {hasSpecialCharacters: true}), + Validators.minLength(8) + ])) + }, { + validators: PasswordValidators.passwordMatch('newPassword', 'confirmPassword') + }); + + get f() { + return this.changePasswordForm.controls; + } + + onSubmit() { + if (this.changePasswordForm.invalid) { + return; + } + + const changePasswordModel = this.changePasswordForm.value; + + this._userService.changeUserPassword(changePasswordModel).subscribe({ + next: ((response: boolean) => { + if (response) { + Swal.fire('Change password', 'Password changed successfully. You will be logged out automatically', 'success') + .then(() => this._authenticationService.logout()); + } + }), + error: (error: HttpErrorResponse) => { + console.log(error); + this._toastr.error(error.error.name ?? 'Something went wrong'); + } + }); + } +} diff --git a/Ui/src/app/pages/custom-event/custom-event.component.css b/Ui/src/app/pages/custom-event/custom-event.component.css new file mode 100644 index 0000000..e69de29 diff --git a/Ui/src/app/pages/custom-event/custom-event.component.html b/Ui/src/app/pages/custom-event/custom-event.component.html new file mode 100644 index 0000000..caaccea --- /dev/null +++ b/Ui/src/app/pages/custom-event/custom-event.component.html @@ -0,0 +1,6 @@ +
+

Custom Event

+ + + +
diff --git a/Ui/src/app/pages/custom-event/custom-event.component.ts b/Ui/src/app/pages/custom-event/custom-event.component.ts new file mode 100644 index 0000000..5052a4c --- /dev/null +++ b/Ui/src/app/pages/custom-event/custom-event.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-custom-event', + templateUrl: './custom-event.component.html', + styleUrl: './custom-event.component.css' +}) +export class CustomEventComponent { + +} diff --git a/Ui/src/app/pages/desert-storm/desert-storm-detail/desert-storm-detail.component.css b/Ui/src/app/pages/desert-storm/desert-storm-detail/desert-storm-detail.component.css new file mode 100644 index 0000000..d1a88bd --- /dev/null +++ b/Ui/src/app/pages/desert-storm/desert-storm-detail/desert-storm-detail.component.css @@ -0,0 +1,3 @@ +.text-color { + color: #43c315; +} diff --git a/Ui/src/app/pages/desert-storm/desert-storm-detail/desert-storm-detail.component.html b/Ui/src/app/pages/desert-storm/desert-storm-detail/desert-storm-detail.component.html new file mode 100644 index 0000000..e4d4c16 --- /dev/null +++ b/Ui/src/app/pages/desert-storm/desert-storm-detail/desert-storm-detail.component.html @@ -0,0 +1,65 @@ +
+ +
+ +
+ + @if (desertStormDetail) { +
+
+
Week {{desertStormDetail.eventDate | week}} / {{desertStormDetail.eventDate | date: 'yyyy'}}
+
{{desertStormDetail.won ? 'VICTORY' : 'DEFEAT'}}
+
+
+
Opponent: {{desertStormDetail.opponentName}}
+

Server: {{desertStormDetail.opponentServer}}

+

Opponent participants: {{desertStormDetail.opposingParticipants}}

+

Allianz participants: {{desertStormDetail.participants | number}}

+
+
+

Creator: {{desertStormDetail.createdBy}}

+ @if (desertStormDetail.modifiedOn) { +

Modified: {{desertStormDetail.modifiedOn | date: 'dd.MM.yyyy HH:mm'}} + by {{desertStormDetail.modifiedBy}}

+ } +
+
+
+

Registered: {{registeredPlayers}}

+

Start player: {{startedPlayers}}

+

Participated: {{participatedPlayers}}

+
+
+

Players

+
+
+ @for (player of desertStormDetail.desertStormParticipants; track player.id; let i = $index) { + @if (player.registered) { +
+
+
{{player.playerName}}
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ } + } +
+
+
+
+ } +
diff --git a/Ui/src/app/pages/desert-storm/desert-storm-detail/desert-storm-detail.component.ts b/Ui/src/app/pages/desert-storm/desert-storm-detail/desert-storm-detail.component.ts new file mode 100644 index 0000000..9618788 --- /dev/null +++ b/Ui/src/app/pages/desert-storm/desert-storm-detail/desert-storm-detail.component.ts @@ -0,0 +1,49 @@ +import {Component, inject, OnInit} from '@angular/core'; +import {ActivatedRoute} from "@angular/router"; +import {DesertStormService} from "../../../services/desert-storm.service"; +import {DesertStormDetailModel} from "../../../models/desertStorm.model"; + +@Component({ + selector: 'app-desert-storm-detail', + templateUrl: './desert-storm-detail.component.html', + styleUrl: './desert-storm-detail.component.css' +}) +export class DesertStormDetailComponent implements OnInit { + + private readonly _activatedRote: ActivatedRoute = inject(ActivatedRoute); + private readonly _desertStormService: DesertStormService = inject(DesertStormService); + + public desertStormId!: string; + public desertStormDetail: DesertStormDetailModel | undefined; + public registeredPlayers: number = 0; + public participatedPlayers: number = 0; + public startedPlayers: number = 0; + + ngOnInit() { + + this.desertStormId = this._activatedRote.snapshot.params['id']; + + this.getDesertStormDetail(this.desertStormId); + } + + getDesertStormDetail(desertStormId: string) { + this._desertStormService.getDesertStormDetail(desertStormId).subscribe({ + next: (desertStormDetail: DesertStormDetailModel) => { + if (desertStormDetail) { + this.desertStormDetail = desertStormDetail; + desertStormDetail.desertStormParticipants.forEach((d) => { + if (d.registered) { + this.registeredPlayers++; + } + if (d.participated) { + this.participatedPlayers++; + } + if (d.startPlayer) { + this.startedPlayers++; + } + }) + } + } + }); + } +} diff --git a/Ui/src/app/pages/desert-storm/desert-storm.component.css b/Ui/src/app/pages/desert-storm/desert-storm.component.css new file mode 100644 index 0000000..e69de29 diff --git a/Ui/src/app/pages/desert-storm/desert-storm.component.html b/Ui/src/app/pages/desert-storm/desert-storm.component.html new file mode 100644 index 0000000..fed8c54 --- /dev/null +++ b/Ui/src/app/pages/desert-storm/desert-storm.component.html @@ -0,0 +1,148 @@ +
+

Dessert Storm

+ +
+ +
+ + @if (!isCreateDessertStorm) { + @if (!currentWeekDuelExists) { +
+ +
+ } @else { +
+ +
+ } + } + + @if (isCreateDessertStorm) { +
+
+ + +
+ +
+ + + @if (f['opponentName'].invalid && (f['opponentName'].dirty || f['opponentName'].touched)) { +
+ @if (f['opponentName'].hasError('required')) { +

Opponent name is required

+ } +
+ } +
+ +
+ + + @if (f['opponentServer'].invalid && (f['opponentServer'].dirty || f['opponentServer'].touched)) { +
+ @if (f['opponentServer'].hasError('required')) { +

opponentServer is required

+ } +
+ } +
+ +
+ + + @if (f['OpposingParticipants'].invalid && (f['OpposingParticipants'].dirty || f['OpposingParticipants'].touched)) { +
+ @if (f['opponentPower'].hasError('required')) { +

OpposingParticipants is required

+ } +
+ } +
+ +
+ + +
+ + @if (desertStormPlayers.length <= 0) { +
+

Please add participants

+
+ } @else { +
+

{{selectedPlayers}} player(s) selected

+
+ } + +
+ +
+ +
+ + +
+
+ } + + @if(!isCreateDessertStorm) { + @if (desertStorms.length > 0) { +
+ + + + + + + + + + + + + + @for (desertStorm of desertStorms; track desertStorm.id) { + + + + + + + + + + } + +
Event DateOpponent nameOpponent serverOpposing participantsAllianz participantsWonAction
{{desertStorm.eventDate | date: 'dd.MM.yyyy'}}{{desertStorm.opponentName}}{{desertStorm.opponentServer}}{{desertStorm.opposingParticipants}}{{desertStorm.participants}} + + +
+ + + +
+
+
+ } @else { + + } + } +
diff --git a/Ui/src/app/pages/desert-storm/desert-storm.component.ts b/Ui/src/app/pages/desert-storm/desert-storm.component.ts new file mode 100644 index 0000000..3e3b9ee --- /dev/null +++ b/Ui/src/app/pages/desert-storm/desert-storm.component.ts @@ -0,0 +1,254 @@ +import {Component, inject, OnInit} from '@angular/core'; +import {CreateDesertStormModel, DesertStormDetailModel, DesertStormModel} from "../../models/desertStorm.model"; +import {FormControl, FormGroup, Validators} from "@angular/forms"; +import {JwtTokenService} from "../../services/jwt-token.service"; +import {DesertStormService} from "../../services/desert-storm.service"; +import {WeekPipe} from "../../helpers/week.pipe"; +import {Router} from "@angular/router"; +import Swal from "sweetalert2"; +import {NgbModal} from "@ng-bootstrap/ng-bootstrap"; +import { + DesertStormParticipantsModalComponent +} from "../../modals/desert-storm-participants-modal/desert-storm-participants-modal.component"; +import {DesertStormParticipantService} from "../../services/desert-storm-participant.service"; +import { + CreateDesertStormParticipantModel, + DesertStormParticipantModel +} from "../../models/desertStormParticipant.model"; +import {ToastrService} from "ngx-toastr"; +import {forkJoin, Observable} from "rxjs"; + +@Component({ + selector: 'app-desert-storm', + templateUrl: './desert-storm.component.html', + styleUrl: './desert-storm.component.css' +}) +export class DesertStormComponent implements OnInit { + + private readonly _weekPipe: WeekPipe = new WeekPipe(); + private readonly _tokenService: JwtTokenService = inject(JwtTokenService); + private readonly _desertStormService: DesertStormService = inject(DesertStormService); + private readonly _desertStormParticipantService: DesertStormParticipantService = inject(DesertStormParticipantService); + private readonly _router: Router = inject(Router); + private readonly _modalService: NgbModal = inject(NgbModal); + private readonly _toastr: ToastrService = inject(ToastrService); + + isCreateDessertStorm: boolean = false; + public desertStorms: DesertStormModel[] = []; + currentDate: Date = new Date(); + currentWeekDuelExists: boolean = false; + desertStormPlayers: {playerId: string, playerName: string, participated: boolean, registered: boolean, startPlayer: boolean}[] = []; + selectedPlayers: number = 0; + desertStormDetailModel: DesertStormDetailModel | undefined; + + desertStormForm!: FormGroup; + isUpdate: boolean = false; + + get f() { + return this.desertStormForm.controls; + } + + ngOnInit() { + this.getDesertStorms(10); + } + + getDesertStorms(take: number) { + this._desertStormService.getAllianceDesertStorms(this._tokenService.getAllianceId()!, take).subscribe({ + next: (response) => { + if (response) { + response.forEach((desertStorm: DesertStormModel) => { + if (this._weekPipe.transform(desertStorm.eventDate) === this._weekPipe.transform(new Date())) { + this.currentWeekDuelExists = true; + } + }) + this.desertStorms = response; + } else { + this.desertStorms = []; + this.currentWeekDuelExists = false; + } + }}); + } + + onCreateEvent() { + this.createDesertStormForm(); + this.isCreateDessertStorm = true; + } + + createDesertStormForm(desertStormModel: DesertStormModel | null = null): void { + const d = desertStormModel ? new Date(desertStormModel.eventDate) : new Date(); + this.desertStormForm = new FormGroup({ + id: new FormControl(desertStormModel ? desertStormModel.id : ''), + allianceId: new FormControl(desertStormModel ? desertStormModel.allianceId : this._tokenService.getAllianceId()!, [Validators.required]), + eventDate: new FormControl(new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate())).toISOString().substring(0, 10)), + won: new FormControl(desertStormModel ? desertStormModel.won : false), + opponentName: new FormControl(desertStormModel ? desertStormModel.opponentName : ''), + opponentServer: new FormControl(desertStormModel ? desertStormModel.opponentServer : null), + OpposingParticipants: new FormControl(desertStormModel ? desertStormModel.opposingParticipants : null), + }) + } + + onSubmit() { + if (this.desertStormForm.invalid) { + return; + } + + if (this.isUpdate) { + this.updateDesertStorm(); + return; + } + + const createEvent: CreateDesertStormModel = this.desertStormForm.value as CreateDesertStormModel; + this._desertStormService.createDesertStorm(createEvent).subscribe({ + next: (response) => { + if (response) { + this.insertDesertStormParticipants(response.id); + } + } + }); + } + + onDesertStormDetail(desertStorm: DesertStormModel) { + this._router.navigate(['desert-storm-detail', desertStorm.id]).then(); + } + + onDeleteDesertStorm(desertStorm: DesertStormModel) { + Swal.fire({ + title: "Delete Desert storm ?", + text: `Do you really want to delete the Desert storm`, + icon: "warning", + showCancelButton: true, + confirmButtonColor: "#3085d6", + cancelButtonColor: "#d33", + confirmButtonText: "Yes, delete it!" + }).then((result) => { + if (result.isConfirmed) { + this._desertStormService.deleteDesertStorm(desertStorm.id).subscribe({ + next: ((response) => { + if (response) { + Swal.fire({ + title: "Deleted!", + text: "Desert storm has been deleted", + icon: "success" + }).then(_ => this.getDesertStorms(10)); + } + }), + error: (error: Error) => { + console.log(error); + } + }); + } + }); + } + + onAddParticipants() { + const modalRef = this._modalService.open(DesertStormParticipantsModalComponent, + {animation: true, backdrop: 'static', centered: true, size: 'lg', scrollable: true}); + if (this.desertStormPlayers.length > 0) { + modalRef.componentInstance.players = [...this.desertStormPlayers]; + } + modalRef.componentInstance.allianceId = this._tokenService.getAllianceId(); + modalRef.closed.subscribe({ + next: ((response: {playerId: string, playerName: string, participated: boolean, registered: boolean, startPlayer: boolean}[]) => { + if (response) { + this.selectedPlayers = 0; + this.desertStormPlayers = response; + response.forEach((d => { + if (d.registered) { + this.selectedPlayers++; + } + })) + } + }) + }) + } + + onCancel() { + this.isCreateDessertStorm = false; + this.selectedPlayers = 0; + this.desertStormPlayers = []; + } + + private insertDesertStormParticipants(desertStormId: string) { + const desertStormParticipants: CreateDesertStormParticipantModel[] = []; + + this.desertStormPlayers.forEach(player => { + const desertStormParticipant: CreateDesertStormParticipantModel = { + desertStormId: desertStormId, + participated: player.participated, + playerId: player.playerId, + startPlayer: player.startPlayer, + registered: player.registered, + } + desertStormParticipants.push(desertStormParticipant); + }); + + this._desertStormParticipantService.insertDesertStormOParticipants(desertStormParticipants).subscribe({ + next: (() => { + this._toastr.success('Successfully created!', 'Successfully'); + this.onCancel(); + this.getDesertStorms(10); + }) + }) + } + + onEditDesertStorm(desertStorm: DesertStormModel) { + this._desertStormService.getDesertStormDetail(desertStorm.id).subscribe({ + next: (response) => { + if (response) { + this.desertStormPlayers = structuredClone(response.desertStormParticipants); + this.createDesertStormForm(response); + response.desertStormParticipants.forEach((d) => { + if (d.registered) { + this.selectedPlayers++; + } + }) + this.isCreateDessertStorm = true; + this.isUpdate = true; + this.desertStormDetailModel = response; + } + } + }) + } + + updateDesertStorm() { + const toUpdate: any[] = []; + const desertStorm: DesertStormModel = this.desertStormForm.value as DesertStormModel; + this.desertStormPlayers.forEach(p => { + const player = this.desertStormDetailModel!.desertStormParticipants.find(d => d.playerId === p.playerId)!; + if (player.participated !== p.participated || player.registered !== p.registered || player.startPlayer !== p.startPlayer) { + toUpdate.push(p); + } + }); + this._desertStormService.updateDesertStorm(desertStorm.id, desertStorm).subscribe({ + next: ((response) => { + if (response) { + this.updateDesertStormParticipants(toUpdate); + } + }) + }); + } + + private updateDesertStormParticipants(desertStormParticipants: DesertStormParticipantModel[]) { + if (desertStormParticipants.length <= 0) { + this._toastr.success('Successfully updated!', 'Successfully'); + this.onCancel(); + this.getDesertStorms(10); + return; + } + const requests: Observable[] = []; + + desertStormParticipants.forEach((participant) => { + const request = this._desertStormParticipantService.updateDesertStormParticipant(participant.id, participant); + requests.push(request); + }) + forkJoin(requests).subscribe({ + next: ((response) => { + if (response) { + this._toastr.success('Successfully updated!', 'Successfully'); + this.onCancel(); + this.getDesertStorms(10); + } + }) + }) + } +} diff --git a/Ui/src/app/pages/marshal-guard/marshal-guard-detail/marshal-guard-detail.component.css b/Ui/src/app/pages/marshal-guard/marshal-guard-detail/marshal-guard-detail.component.css new file mode 100644 index 0000000..fd70b4c --- /dev/null +++ b/Ui/src/app/pages/marshal-guard/marshal-guard-detail/marshal-guard-detail.component.css @@ -0,0 +1,14 @@ +.custom-rating-icon { + font-size: 1.5rem; + padding-right: 0.1rem; + color: #b0c4de; +} +.filled { + color: #1e90ff; +} +.low { + color: #deb0b0; +} +.filled.low { + color: #ff1e1e; +} diff --git a/Ui/src/app/pages/marshal-guard/marshal-guard-detail/marshal-guard-detail.component.html b/Ui/src/app/pages/marshal-guard/marshal-guard-detail/marshal-guard-detail.component.html new file mode 100644 index 0000000..0b1ea95 --- /dev/null +++ b/Ui/src/app/pages/marshal-guard/marshal-guard-detail/marshal-guard-detail.component.html @@ -0,0 +1,48 @@ +
+ +
+ +
+ + @if (marshalGuardDetail) { +
+
+
{{marshalGuardDetail.eventDate | date: 'dd.MM.yyyy'}}
+
+
+
Level: {{marshalGuardDetail.level}}
+

Participation rate:

+

Reward phase:
+ + + + + +

+
+
+

Creator: {{marshalGuardDetail.createdBy}}

+ @if (marshalGuardDetail.modifiedOn) { +

Modified: {{marshalGuardDetail.modifiedOn | date: 'dd.MM.yyyy HH:mm'}} + by {{marshalGuardDetail.modifiedBy}}

+ } +
+
+

Participants

+
+
+ @for (player of marshalGuardDetail.marshalGuardParticipants; track player.id) { + + @if (player.participated) { +
+

{{player.playerName}}

+
+ } + } +
+
+
+
+ } +
+ diff --git a/Ui/src/app/pages/marshal-guard/marshal-guard-detail/marshal-guard-detail.component.ts b/Ui/src/app/pages/marshal-guard/marshal-guard-detail/marshal-guard-detail.component.ts new file mode 100644 index 0000000..9127319 --- /dev/null +++ b/Ui/src/app/pages/marshal-guard/marshal-guard-detail/marshal-guard-detail.component.ts @@ -0,0 +1,35 @@ +import {Component, inject, OnInit} from '@angular/core'; +import {ActivatedRoute} from "@angular/router"; +import {MarshalGuardService} from "../../../services/marshal-guard.service"; +import {MarshalGuardDetailModel} from "../../../models/marshalGuard.model"; + +@Component({ + selector: 'app-marshal-guard-detail', + templateUrl: './marshal-guard-detail.component.html', + styleUrl: './marshal-guard-detail.component.css' +}) +export class MarshalGuardDetailComponent implements OnInit { + + private readonly _activatedRote: ActivatedRoute = inject(ActivatedRoute); + private readonly _marshalGuardService: MarshalGuardService = inject(MarshalGuardService); + + public marshalGuardId!: string; + public marshalGuardDetail: MarshalGuardDetailModel | undefined; + + + ngOnInit() { + this.marshalGuardId = this._activatedRote.snapshot.params['id']; + + this.getMarshalGuardDetail(this.marshalGuardId); + } + + getMarshalGuardDetail(marshalGuardId: string) { + this._marshalGuardService.getMarshalGuardDetail(marshalGuardId).subscribe({ + next: ((response) => { + if (response) { + this.marshalGuardDetail = response; + } + }) + }); + } +} diff --git a/Ui/src/app/pages/marshal-guard/marshal-guard.component.css b/Ui/src/app/pages/marshal-guard/marshal-guard.component.css new file mode 100644 index 0000000..61f0f94 --- /dev/null +++ b/Ui/src/app/pages/marshal-guard/marshal-guard.component.css @@ -0,0 +1,29 @@ +.check-box-container { + display: flex; + flex-wrap: wrap; + gap: 10px; +} + +.check-box-container .form-check { + flex: 1 1 calc(50% - 10px); + box-sizing: border-box; +} + +.text-color { + color: #43c315; +} + +.custom-rating { + font-size: 1.0rem; + padding-right: 0.1rem; + color: #b0c4de; +} +.filled { + color: #1e90ff; +} +.low { + color: #deb0b0; +} +.filled.low { + color: #ff1e1e; +} diff --git a/Ui/src/app/pages/marshal-guard/marshal-guard.component.html b/Ui/src/app/pages/marshal-guard/marshal-guard.component.html new file mode 100644 index 0000000..f42cd9c --- /dev/null +++ b/Ui/src/app/pages/marshal-guard/marshal-guard.component.html @@ -0,0 +1,124 @@ +
+

Marshal Guard

+ + @if (!isCreateMarshalGuard) { +
+ +
+ } + + @if (isCreateMarshalGuard) { +
+
+ + + @if (f['eventDate'].invalid && (f['eventDate'].dirty || f['eventDate'].touched)) { +
+ @if (f['eventDate'].hasError('required')) { +

eventDate is required

+ } +
+ } +
+
+ + + @if (f['level'].invalid && (f['level'].dirty || f['level'].touched)) { +
+ @if (f['level'].hasError('required')) { +

Level is required

+ } +
+ } +
+
+ + +
+
+ + +
+ + @if (playerSelected) { +
+

{{participatedPlayer}} players participated

+

{{notParticipatedPlayer}} player did not participated

+
+ } @else { +
+

Please add participants

+
+ } + +
+ +
+
+ + +
+
+ } + + @if(!isCreateMarshalGuard) { + @if (marshalGuards.length > 0) { +
+ + + + + + + + + + + + + + @for (marshalGuard of marshalGuards; track marshalGuard.id) { + + + + + + + + + + } + +
Event DateLevelReward PhaseAlliance SizeParticipating playersCreatorAction
{{marshalGuard.eventDate | date: 'dd.MM.yyyy'}}{{marshalGuard.level}} + + + + + + {{marshalGuard.allianceSize}}{{marshalGuard.participants}}{{marshalGuard.createdBy}} +
+ + + +
+
+
+ } @else { + + } + } +
diff --git a/Ui/src/app/pages/marshal-guard/marshal-guard.component.ts b/Ui/src/app/pages/marshal-guard/marshal-guard.component.ts new file mode 100644 index 0000000..de84781 --- /dev/null +++ b/Ui/src/app/pages/marshal-guard/marshal-guard.component.ts @@ -0,0 +1,306 @@ +import {Component, inject, OnInit} from '@angular/core'; +import {FormControl, FormGroup, Validators} from "@angular/forms"; +import {PlayerService} from "../../services/player.service"; +import {JwtTokenService} from "../../services/jwt-token.service"; +import {PlayerModel} from "../../models/player.model"; +import {NgbModal} from "@ng-bootstrap/ng-bootstrap"; +import {MarshalGuardModalComponent} from "../../modals/marshal-guard-modal/marshal-guard-modal.component"; +import { + CreateMarshalGuardModel, + MarshalGuardDetailModel, + MarshalGuardModel, + UpdateMarshalGuardModel +} from "../../models/marshalGuard.model"; +import {MarshalGuardService} from "../../services/marshal-guard.service"; +import {MarshalGuardParticipantService} from "../../services/marshal-guard-participant.service"; +import { + CreateMarshalGuardParticipantModel, MarshalGuardParticipantModel +} from "../../models/marshalGuardParticipant.model"; +import {ToastrService} from "ngx-toastr"; +import Swal from "sweetalert2"; +import {Router} from "@angular/router"; +import {forkJoin, Observable} from "rxjs"; + +@Component({ + selector: 'app-marshal-guard', + templateUrl: './marshal-guard.component.html', + styleUrl: './marshal-guard.component.css' +}) +export class MarshalGuardComponent implements OnInit { + + private readonly _playerService: PlayerService = inject(PlayerService); + private readonly _marshalGuardService: MarshalGuardService = inject(MarshalGuardService); + private readonly _marshalGuardParticipantService: MarshalGuardParticipantService = inject(MarshalGuardParticipantService); + private readonly _tokenService: JwtTokenService = inject(JwtTokenService); + private readonly _modalService: NgbModal = inject(NgbModal); + private readonly _toastr: ToastrService = inject(ToastrService); + private readonly _router: Router = inject(Router); + + public marshalGuardForm!: FormGroup; + isCreateMarshalGuard: boolean = false + isUpdate: boolean = false; + public alliancePlayers: PlayerModel[] = []; + public playerParticipated: { playerId: string, playerName: string, participated: boolean }[] = []; + + private marshalGuardDetail!: MarshalGuardDetailModel; + + public participantToUpdate: MarshalGuardParticipantModel[] = []; + public participatedPlayer: number = 0; + public notParticipatedPlayer: number = 0; + public playerSelected: boolean = false; + public marshalGuards: MarshalGuardModel[] = []; + + private allianceId: string = this._tokenService.getAllianceId()!; + + get f() { + return this.marshalGuardForm.controls; + } + + ngOnInit() { + this.getMarshalGuards(10); + } + + public getMarshalGuards(take: number) { + this._marshalGuardService.getAllianceMarshalGuards(this.allianceId, take).subscribe({ + next: ((response) => { + if (response) { + this.marshalGuards = response; + } else { + this.marshalGuards = []; + } + }), + error: ((error) => { + console.log(error); + this._toastr.error('Could not load marshalGuards', 'Error loading marshalGuards'); + }) + }); + } + + public getAlliancePlayers() { + this._playerService.getAlliancePlayer(this.allianceId).subscribe({ + next: ((response) => { + if (response) { + this.alliancePlayers = response; + this.createMarshalGuardForm(false); + } + }) + }); + } + + public createMarshalGuardForm(isUpdate: boolean, marshalGuard: MarshalGuardDetailModel | null = null) { + if (isUpdate) { + this.playerSelected = true; + this.playerParticipated = [...marshalGuard!.marshalGuardParticipants]; + this.participatedPlayer = marshalGuard!.participants; + this.notParticipatedPlayer = marshalGuard!.allianceSize - marshalGuard!.participants; + } else { + this.playerSelected = false; + } + const d = isUpdate ? new Date(marshalGuard!.eventDate) : new Date(); + this.marshalGuardForm = new FormGroup({ + id: new FormControl(isUpdate ? marshalGuard!.id : ''), + allianceId: new FormControl(this.allianceId), + eventDate: new FormControl(new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate())).toISOString().substring(0, 10)), + level: new FormControl(isUpdate ? marshalGuard!.level : null, [Validators.required]), + rewardPhase: new FormControl(isUpdate ? marshalGuard!.rewardPhase : 1), + allianceSize: new FormControl(isUpdate ? marshalGuard!.allianceSize : this.alliancePlayers.length), + }); + this.marshalGuardForm.get('allianceSize')?.disable(); + this.isCreateMarshalGuard = true; + } + + onCreateEvent() { + this.getAlliancePlayers(); + } + + onCancel() { + this.isCreateMarshalGuard = false; + } + + onAddParticipants() { + const modalRef = this._modalService.open(MarshalGuardModalComponent, + {animation: true, backdrop: 'static', centered: true, size: 'lg', scrollable: true}); + if (this.playerSelected) { + modalRef.componentInstance.players = [...this.playerParticipated]; + } + modalRef.componentInstance.allianceId = this.allianceId; + modalRef.closed.subscribe({ + next: ((response: any) => { + if (response) { + this.participatedPlayer = 0; + this.notParticipatedPlayer = 0; + this.playerSelected = true; + response.forEach((player: { playerId: string, playerName: string, participated: boolean }) => { + if (player.participated) { + this.participatedPlayer++; + } else { + this.notParticipatedPlayer++; + } + }) + this.playerParticipated = response; + } + }) + }) + } + + + onSubmit() { + if (this.isUpdate) { + this.updateMarshalGuard(); + return; + } + const createMarshalGuard: CreateMarshalGuardModel = { + allianceId: this.marshalGuardForm.controls['allianceId'].value, + level: this.marshalGuardForm.controls['level'].value, + rewardPhase: this.marshalGuardForm.controls['rewardPhase'].value, + allianceSize: this.marshalGuardForm.controls['allianceSize'].value, + eventDate: this.marshalGuardForm.controls['eventDate'].value, + } + + this._marshalGuardService.insertMarshalGuards(createMarshalGuard).subscribe({ + next: ((response) => { + if (response) { + if (this.playerParticipated.length > 0) { + this.insertMarshalGuardParticipants(response.id); + this._toastr.success('Successfully created marshalGuard', 'Successfully created marshalGuard'); + } + } + }) + }); + } + + private insertMarshalGuardParticipants(marshalGuardId: string) { + const marshalGuardParticipants: CreateMarshalGuardParticipantModel[] = []; + + this.playerParticipated.forEach((player) => { + const createMarshalGuardParticipant: CreateMarshalGuardParticipantModel = { + marshalGuardId: marshalGuardId, + participated: player.participated, + playerId: player.playerId + }; + marshalGuardParticipants.push(createMarshalGuardParticipant); + }); + + this._marshalGuardParticipantService.insertMarshalGuardOParticipants(marshalGuardParticipants).subscribe({ + next: (() => { + this.onCancel(); + this.getMarshalGuards(10); + this.playerParticipated = []; + }) + }) + + } + + private updateMarshalGuard() { + let participatedPlayers: number = this.marshalGuardDetail.participants; + this.marshalGuardDetail.marshalGuardParticipants.forEach((participant) => { + const playerToUpdate = this.playerParticipated.find(p => p.playerId === participant.playerId); + if (playerToUpdate) { + if (playerToUpdate.participated !== participant.participated) { + if (playerToUpdate.participated) { + participatedPlayers++; + } else { + participatedPlayers--; + } + this.participantToUpdate.push({ + playerId: participant.playerId, + marshalGuardId: participant.marshalGuardId, + participated: playerToUpdate.participated, + id: participant.id, + playerName: participant.playerName + }); + } + } + }); + const updateMarshalGuard: UpdateMarshalGuardModel = { + id: this.marshalGuardForm.controls['id'].value, + allianceId: this.marshalGuardForm.controls['allianceId'].value, + level: this.marshalGuardForm.controls['level'].value, + rewardPhase: this.marshalGuardForm.controls['rewardPhase'].value, + eventDate: this.marshalGuardForm.controls['eventDate'].value, + participants: participatedPlayers + } + + this._marshalGuardService.updateMarshalGuard(updateMarshalGuard.id, updateMarshalGuard).subscribe({ + next: ((response) => { + if (response) { + this.updateMarshalGuardParticipants(); + } + }) + }); + } + + private updateMarshalGuardParticipants() { + if (this.participantToUpdate.length <= 0) { + this.onCancel(); + this.getMarshalGuards(10); + this.playerParticipated = []; + this.participantToUpdate = []; + this._toastr.success('Successfully updated marshalGuard', 'Update marshalGuard') + return; + } + const requests: Observable[] = []; + + this.participantToUpdate.forEach((participant) => { + const request = this._marshalGuardParticipantService.updateMarshalGuardParticipant(participant.id, participant); + requests.push(request); + }) + forkJoin(requests).subscribe({ + next: ((response) => { + if (response) { + this.onCancel(); + this.getMarshalGuards(10); + this.playerParticipated = []; + this.participantToUpdate = []; + this._toastr.success('Successfully updated marshalGuard', 'Update marshalGuard'); + } + }) + }) + } + + onEditMarshalGuard(marshalGuard: MarshalGuardModel) { + this._marshalGuardService.getMarshalGuardDetail(marshalGuard.id).subscribe({ + next: ((response) => { + if (response) { + this.isUpdate = true; + this.marshalGuardDetail = structuredClone(response); + this.participantToUpdate = []; + this.createMarshalGuardForm(true, response); + } + }) + }); + } + + onDeleteMarshalGuard(marshalGuard: MarshalGuardModel) { + Swal.fire({ + title: "Delete Marshal guard ?", + text: `Do you really want to delete the marshal guard`, + icon: "warning", + showCancelButton: true, + confirmButtonColor: "#3085d6", + cancelButtonColor: "#d33", + confirmButtonText: "Yes, delete it!" + }).then((result) => { + if (result.isConfirmed) { + this._marshalGuardService.deleteMarshalGuard(marshalGuard.id).subscribe({ + next: ((response) => { + if (response) { + Swal.fire({ + title: "Deleted!", + text: "Marshal guards has been deleted", + icon: "success" + }).then(_ => this.getMarshalGuards(10)); + } + }), + error: (error: Error) => { + console.log(error); + } + }); + } + }); + } + + onGoToMarshalGuardDetail(marshalGuard: MarshalGuardModel) { + this._router.navigate(['marshal-guard-detail', marshalGuard.id]).then(); + } +} diff --git a/Ui/src/app/pages/player-information/player-info-custom-event/player-info-custom-event.component.css b/Ui/src/app/pages/player-information/player-info-custom-event/player-info-custom-event.component.css new file mode 100644 index 0000000..e69de29 diff --git a/Ui/src/app/pages/player-information/player-info-custom-event/player-info-custom-event.component.html b/Ui/src/app/pages/player-information/player-info-custom-event/player-info-custom-event.component.html new file mode 100644 index 0000000..078fa5d --- /dev/null +++ b/Ui/src/app/pages/player-information/player-info-custom-event/player-info-custom-event.component.html @@ -0,0 +1 @@ + diff --git a/Ui/src/app/pages/player-information/player-info-custom-event/player-info-custom-event.component.ts b/Ui/src/app/pages/player-information/player-info-custom-event/player-info-custom-event.component.ts new file mode 100644 index 0000000..214cb76 --- /dev/null +++ b/Ui/src/app/pages/player-information/player-info-custom-event/player-info-custom-event.component.ts @@ -0,0 +1,10 @@ +import {Component} from '@angular/core'; + +@Component({ + selector: 'app-player-info-custom-event', + templateUrl: './player-info-custom-event.component.html', + styleUrl: './player-info-custom-event.component.css' +}) +export class PlayerInfoCustomEventComponent { + +} diff --git a/Ui/src/app/pages/player-information/player-info-desert-storm/player-info-desert-storm.component.css b/Ui/src/app/pages/player-information/player-info-desert-storm/player-info-desert-storm.component.css new file mode 100644 index 0000000..e69de29 diff --git a/Ui/src/app/pages/player-information/player-info-desert-storm/player-info-desert-storm.component.html b/Ui/src/app/pages/player-information/player-info-desert-storm/player-info-desert-storm.component.html new file mode 100644 index 0000000..078fa5d --- /dev/null +++ b/Ui/src/app/pages/player-information/player-info-desert-storm/player-info-desert-storm.component.html @@ -0,0 +1 @@ + diff --git a/Ui/src/app/pages/player-information/player-info-desert-storm/player-info-desert-storm.component.ts b/Ui/src/app/pages/player-information/player-info-desert-storm/player-info-desert-storm.component.ts new file mode 100644 index 0000000..be3aec0 --- /dev/null +++ b/Ui/src/app/pages/player-information/player-info-desert-storm/player-info-desert-storm.component.ts @@ -0,0 +1,10 @@ +import {Component} from '@angular/core'; + +@Component({ + selector: 'app-player-info-desert-storm', + templateUrl: './player-info-desert-storm.component.html', + styleUrl: './player-info-desert-storm.component.css' +}) +export class PlayerInfoDesertStormComponent { + +} diff --git a/Ui/src/app/pages/player-information/player-info-marshal-guard/player-info-marshal-guard.component.css b/Ui/src/app/pages/player-information/player-info-marshal-guard/player-info-marshal-guard.component.css new file mode 100644 index 0000000..e69de29 diff --git a/Ui/src/app/pages/player-information/player-info-marshal-guard/player-info-marshal-guard.component.html b/Ui/src/app/pages/player-information/player-info-marshal-guard/player-info-marshal-guard.component.html new file mode 100644 index 0000000..df74bdf --- /dev/null +++ b/Ui/src/app/pages/player-information/player-info-marshal-guard/player-info-marshal-guard.component.html @@ -0,0 +1,19 @@ +@if (playerGuardsLoaded) { +

Participated {{ playerGuards }} Marshal Guards of + last {{ totalMarshalGuards }}

+ +} +
+
+ +
+
+ +
+
+ +
+
diff --git a/Ui/src/app/pages/player-information/player-info-marshal-guard/player-info-marshal-guard.component.ts b/Ui/src/app/pages/player-information/player-info-marshal-guard/player-info-marshal-guard.component.ts new file mode 100644 index 0000000..444ee16 --- /dev/null +++ b/Ui/src/app/pages/player-information/player-info-marshal-guard/player-info-marshal-guard.component.ts @@ -0,0 +1,67 @@ +import {Component, inject, Input} from '@angular/core'; +import {MarshalGuardParticipantService} from "../../../services/marshal-guard-participant.service"; +import {ToastrService} from "ngx-toastr"; +import {MarshalGuardParticipantModel} from "../../../models/marshalGuardParticipant.model"; + +@Component({ + selector: 'app-player-info-marshal-guard', + templateUrl: './player-info-marshal-guard.component.html', + styleUrl: './player-info-marshal-guard.component.css' +}) +export class PlayerInfoMarshalGuardComponent { + + public playerGuardsLoaded: boolean = false; + public marshalType: string = 'success'; + public playerGuards: number = 0; + public totalMarshalGuards: number = 0; + public numberOfLoadMarshalGuards: number = 10; + public progressValue: number = 0; + @Input({required: true}) playerId!: string; + private readonly _marshalGuardParticipantService: MarshalGuardParticipantService = inject(MarshalGuardParticipantService); + private readonly _toastr: ToastrService = inject(ToastrService); + + public onReloadMarshalGuards(): void { + this.getNumberOfParticipants(this.playerId, this.numberOfLoadMarshalGuards); + } + + private getNumberOfParticipants(playerId: string, last: number): void { + this.totalMarshalGuards = 0; + this.playerGuards = 0; + this._marshalGuardParticipantService.getPlayerMarshalGuardParticipants(playerId, last).subscribe({ + next: ((response: MarshalGuardParticipantModel[]) => { + if (response) { + this.playerGuardsLoaded = true; + this.totalMarshalGuards = response.length; + if (this.totalMarshalGuards < last) { + this._toastr.info('Fewer Marshal Guards were held than wanted to be loaded'); + this.numberOfLoadMarshalGuards = response.length; + } + response.forEach((marshalGuardParticipant: MarshalGuardParticipantModel) => { + if (marshalGuardParticipant.participated) { + this.playerGuards++; + } + }); + this.getMarshalValue(); + } + }) + }); + } + + private getMarshalValue(): void { + const percent: number = (this.playerGuards / this.totalMarshalGuards) * 100; + this.setMarshalColor(percent); + this.progressValue = percent; + } + + private setMarshalColor(percent: number): void { + if (percent <= 20) { + this.marshalType = 'danger'; + } else if (percent > 20 && percent <= 50) { + this.marshalType = 'warning'; + } else if (percent > 50 && percent <= 70) { + this.marshalType = 'primary'; + } else { + this.marshalType = 'success'; + } + } +} diff --git a/Ui/src/app/pages/player-information/player-info-vs-duel/player-info-vs-duel.component.css b/Ui/src/app/pages/player-information/player-info-vs-duel/player-info-vs-duel.component.css new file mode 100644 index 0000000..e69de29 diff --git a/Ui/src/app/pages/player-information/player-info-vs-duel/player-info-vs-duel.component.html b/Ui/src/app/pages/player-information/player-info-vs-duel/player-info-vs-duel.component.html new file mode 100644 index 0000000..078fa5d --- /dev/null +++ b/Ui/src/app/pages/player-information/player-info-vs-duel/player-info-vs-duel.component.html @@ -0,0 +1 @@ + diff --git a/Ui/src/app/pages/player-information/player-info-vs-duel/player-info-vs-duel.component.ts b/Ui/src/app/pages/player-information/player-info-vs-duel/player-info-vs-duel.component.ts new file mode 100644 index 0000000..e718777 --- /dev/null +++ b/Ui/src/app/pages/player-information/player-info-vs-duel/player-info-vs-duel.component.ts @@ -0,0 +1,10 @@ +import {Component} from '@angular/core'; + +@Component({ + selector: 'app-player-info-vs-duel', + templateUrl: './player-info-vs-duel.component.html', + styleUrl: './player-info-vs-duel.component.css' +}) +export class PlayerInfoVsDuelComponent { + +} diff --git a/Ui/src/app/pages/player-information/player-information.component.css b/Ui/src/app/pages/player-information/player-information.component.css new file mode 100644 index 0000000..e69de29 diff --git a/Ui/src/app/pages/player-information/player-information.component.html b/Ui/src/app/pages/player-information/player-information.component.html new file mode 100644 index 0000000..200766f --- /dev/null +++ b/Ui/src/app/pages/player-information/player-information.component.html @@ -0,0 +1,91 @@ +
+ +
+ +
+ @if (currentPlayer) { + +
+
Player Information
+
+
Name: {{ currentPlayer.playerName }}
+

Rank: {{ currentPlayer.rankName }}

+

Headquarter: {{ currentPlayer.level }}

+

Created On: {{ currentPlayer.createdOn | date: 'dd.MM.yyyy HH:mm' }}

+

Created by: {{currentPlayer.createdBy}}

+ @if (currentPlayer.modifiedOn) { +

Modified On: {{ currentPlayer.modifiedOn | date: 'dd.MM.yyyy HH:mm' }}

+

Modified by: {{currentPlayer.modifiedBy}}

+ } +
+ + +
+ +
+
+ +
+ +
+

+ +

+
+
+ +
+
+
+ +
+

+ +

+
+
+ +
+
+
+ +
+

+ +

+
+
+ +
+
+
+ +
+

+ +

+
+
+ +
+
+
+
+ } +
+ diff --git a/Ui/src/app/pages/player-information/player-information.component.ts b/Ui/src/app/pages/player-information/player-information.component.ts new file mode 100644 index 0000000..448e7d9 --- /dev/null +++ b/Ui/src/app/pages/player-information/player-information.component.ts @@ -0,0 +1,68 @@ +import {Component, inject, OnInit} from '@angular/core'; +import {ActivatedRoute} from "@angular/router"; +import {PlayerModel} from "../../models/player.model"; +import {PlayerService} from "../../services/player.service"; +import {NgbModal} from "@ng-bootstrap/ng-bootstrap"; +import {PlayerNoteModalComponent} from "../../modals/player-note-modal/player-note-modal.component"; +import {PlayerAdmonitionModalComponent} from "../../modals/player-admonition-modal/player-admonition-modal.component"; +import {ToastrService} from "ngx-toastr"; + +@Component({ + selector: 'app-player-information', + templateUrl: './player-information.component.html', + styleUrl: './player-information.component.css' +}) +export class PlayerInformationComponent implements OnInit { + + public currentPlayer: PlayerModel | undefined; + public playerId: string = ''; + private readonly _activatedRote: ActivatedRoute = inject(ActivatedRoute); + private readonly _playerService: PlayerService = inject(PlayerService); + private readonly _modalService: NgbModal = inject(NgbModal); + private readonly _toastr: ToastrService = inject(ToastrService); + + ngOnInit() { + this.playerId = this._activatedRote.snapshot.params['id']; + + this.getPlayer(this.playerId); + } + + getPlayer(playerId: string) { + this._playerService.getPlayer(playerId).subscribe({ + next: ((response: PlayerModel) => { + if (response) { + this.currentPlayer = response; + } + }), + error: ((error) => { + console.log(error); + this._toastr.error('Could not load player', 'Error loading player'); + }) + }); + } + + + openPlayerNotes(player: PlayerModel) { + const modalRef = this._modalService.open(PlayerNoteModalComponent, + {animation: true, backdrop: 'static', centered: true, size: 'lg'}); + modalRef.componentInstance.player = player + modalRef.dismissed.subscribe({ + next: (() => { + this.getPlayer(player.id); + }) + }); + } + + openPlayerAdmonitions(currentPlayer: PlayerModel) { + const modalRef = this._modalService.open(PlayerAdmonitionModalComponent, + {animation: true, backdrop: 'static', centered: true, size: 'lg'}); + modalRef.componentInstance.player = currentPlayer + modalRef.dismissed.subscribe({ + next: (() => { + this.getPlayer(currentPlayer + .id); + }) + }); + } + +} diff --git a/Ui/src/app/pages/player/player.component.css b/Ui/src/app/pages/player/player.component.css new file mode 100644 index 0000000..e69de29 diff --git a/Ui/src/app/pages/player/player.component.html b/Ui/src/app/pages/player/player.component.html new file mode 100644 index 0000000..d059ba9 --- /dev/null +++ b/Ui/src/app/pages/player/player.component.html @@ -0,0 +1,85 @@ +
+

Alliance Players

+
+ +
+ +
Members: {{players.length}}/100
+ + @if (players.length > 0) { +
+
+ +
+ +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ + + + + + + + + + + @for (player of filteredPlayers | paginate: { itemsPerPage: itemsPerPage, currentPage: page, id: 'playerTable'}; track player.id) { + + + + + + + } + +
PlayerLevelRankAction
+
+
+ {{player.playerName}} +
+
+ +
+
+
{{player.level}}{{player.rankName}} +
+ + +
+
+
+ + + } @else { + + } + +
diff --git a/Ui/src/app/pages/player/player.component.ts b/Ui/src/app/pages/player/player.component.ts new file mode 100644 index 0000000..4ef9bb6 --- /dev/null +++ b/Ui/src/app/pages/player/player.component.ts @@ -0,0 +1,198 @@ +import { + Component, + inject, + OnInit, +} from '@angular/core'; +import {PlayerService} from "../../services/player.service"; +import { PlayerModel} from "../../models/player.model"; +import {FormControl} from "@angular/forms"; +import {NgbModal} from "@ng-bootstrap/ng-bootstrap"; +import {PlayerEditModalComponent} from "../../modals/player-edit-modal/player-edit-modal.component"; +import Swal from 'sweetalert2' +import {Router} from "@angular/router"; +import {JwtTokenService} from "../../services/jwt-token.service"; + + + +@Component({ + selector: 'app-player', + templateUrl: './player.component.html', + styleUrl: './player.component.css' +}) +export class PlayerComponent implements OnInit { + + + private readonly _playerService: PlayerService = inject(PlayerService); + private readonly _modalService : NgbModal = inject(NgbModal); + private readonly _router: Router = inject(Router); + private readonly _tokenService: JwtTokenService = inject(JwtTokenService); + + private allianceId = this._tokenService.getAllianceId(); + + public players: PlayerModel[] = []; + public tempPlayers: PlayerModel[] = []; + public r1Players: PlayerModel[] = []; + public r2Players: PlayerModel[] = []; + public r3Players: PlayerModel[] = []; + public r4Players: PlayerModel[] = []; + public filteredPlayers: PlayerModel[] = []; + public page: number = 1; + public itemsPerPage: number = 10; + public filter = new FormControl('', { nonNullable: true }); + + + ngOnInit() { + this.filter.valueChanges.subscribe({ + next: ((value) => { + const term = value.toLowerCase(); + this.filteredPlayers = this.tempPlayers.filter(player => { + return player.playerName.toLowerCase().includes(term.toLowerCase()); + }) + }) + }); + + this.getPlayers(this.allianceId!); + } + + getPlayers(allianceId: string) { + this.filteredPlayers = []; + this.players = []; + this.r1Players = []; + this.r2Players = []; + this.r3Players = []; + this.r4Players = []; + this.tempPlayers = []; + this._playerService.getAlliancePlayer(allianceId).subscribe({ + next: ((response: PlayerModel[]): void => { + if (response) { + this.tempPlayers = response; + this.players = response; + this.filteredPlayers = response; + response.filter((player: PlayerModel) => { + if (player.rankName === "R1") { + this.r1Players.push(player); + } else if (player.rankName === "R2") { + this.r2Players.push(player); + } else if (player.rankName === "R3") { + this.r3Players.push(player); + } else if (player.rankName === "R4") { + this.r4Players.push(player); + } + }) + } + }), + error: (error: Error) => { + console.log(error); + } + }); + } + + onEditPlayer(player: PlayerModel) { + const modalRef = this._modalService.open(PlayerEditModalComponent, + {animation: true, backdrop: 'static', centered: true, size: 'lg'}); + modalRef.componentInstance.currentPlayer = player; + modalRef.componentInstance.isUpdate = true; + modalRef.closed.subscribe({ + next: ((response: PlayerModel) => { + if (response) { + this.filter.patchValue(''); + this.getPlayers(response.allianceId); + } + }) + }) + } + + onDeletePlayer(player: PlayerModel) { + Swal.fire({ + title: "Delete Player ?", + text: `Do you really want to delete the player ${player.playerName}`, + icon: "warning", + showCancelButton: true, + confirmButtonColor: "#3085d6", + cancelButtonColor: "#d33", + confirmButtonText: "Yes, delete it!" + }).then((result) => { + if (result.isConfirmed) { + this._playerService.deletePlayer(player.id).subscribe({ + next: ((response) => { + if (response) { + Swal.fire({ + title: "Deleted!", + text: "Player has been deleted", + icon: "success" + }).then(_ => this.getPlayers(this.allianceId!)); + } + }), + error: (error: Error) => { + console.log(error); + } + }); + } + }); + } + + onAddNewPlayer() { + const player: PlayerModel = { + id: '', + notesCount: 0, + admonitionsCount: 0, + allianceId: this.allianceId!, + playerName: '', + level: 0, + rankId: '', + createdOn: new Date(), + rankName: '', + createdBy: '' + } + const modalRef = this._modalService.open(PlayerEditModalComponent, + {animation: true, backdrop: 'static', centered: true, size: 'lg'}); + modalRef.componentInstance.currentPlayer = player; + modalRef.componentInstance.isUpdate = false; + modalRef.closed.subscribe({ + next: ((response: PlayerModel) => { + if (response) { + this.filter.patchValue(''); + this.getPlayers(response.allianceId); + } + }) + }) + } + + onGoToPlayerInformation(player: PlayerModel) { + this._router.navigate(['player-information', player.id]).then(); + } + + onRankFilterChange(event: any) { + switch (event.target.value) { + case 'R1': { + this.filter.patchValue(''); + this.filteredPlayers = this.r1Players; + this.tempPlayers = this.r1Players; + } break; + case 'R2': { + this.filter.patchValue(''); + + this.filteredPlayers = this.r2Players; + this.tempPlayers = this.r2Players; + break; + } + case 'R3': { + this.filter.patchValue(''); + this.filteredPlayers = this.r3Players; + this.tempPlayers = this.r3Players; + break; + } + case 'R4': { + this.filter.patchValue(''); + this.filteredPlayers = this.r4Players; + this.tempPlayers = this.r4Players; + } break; + default: { + this.filter.patchValue(''); + this.filteredPlayers = this.players; + this.tempPlayers = this.players; + break; + } + } + } +} diff --git a/Ui/src/app/pages/vs-duel/vs-duel-detail/vs-duel-detail.component.css b/Ui/src/app/pages/vs-duel/vs-duel-detail/vs-duel-detail.component.css new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/Ui/src/app/pages/vs-duel/vs-duel-detail/vs-duel-detail.component.css @@ -0,0 +1 @@ + diff --git a/Ui/src/app/pages/vs-duel/vs-duel-detail/vs-duel-detail.component.html b/Ui/src/app/pages/vs-duel/vs-duel-detail/vs-duel-detail.component.html new file mode 100644 index 0000000..81152d5 --- /dev/null +++ b/Ui/src/app/pages/vs-duel/vs-duel-detail/vs-duel-detail.component.html @@ -0,0 +1,41 @@ +
+ +
+ +
+ + @if (vsDuelDetail) { +
+
+
Week {{vsDuelDetail.eventDate | week}} / {{vsDuelDetail.eventDate | date: 'yyyy'}}
+
{{vsDuelDetail.won ? 'VICTORY' : 'DEFEAT'}}
+
+
+
Opponent: {{vsDuelDetail.opponentName}}
+

Server: {{vsDuelDetail.opponentServer}}

+

Size: {{vsDuelDetail.opponentSize}}

+

Power: {{vsDuelDetail.opponentPower | number}}

+
+
+

Creator: {{vsDuelDetail.createdBy}}

+ @if (vsDuelDetail.modifiedOn) { +

Modified: {{vsDuelDetail.modifiedOn | date: 'dd.MM.yyyy HH:mm'}} + by {{vsDuelDetail.modifiedBy}}

+ } +
+
+

Players

+
+
+ @for (player of vsDuelDetail.vsDuelParticipants; track player.id) { + +
+

{{player.playerName}} - Weekly points: {{player.weeklyPoints | number}}

+
+ } +
+
+
+
+ } +
diff --git a/Ui/src/app/pages/vs-duel/vs-duel-detail/vs-duel-detail.component.ts b/Ui/src/app/pages/vs-duel/vs-duel-detail/vs-duel-detail.component.ts new file mode 100644 index 0000000..33ad076 --- /dev/null +++ b/Ui/src/app/pages/vs-duel/vs-duel-detail/vs-duel-detail.component.ts @@ -0,0 +1,34 @@ +import {Component, inject, OnInit} from '@angular/core'; +import {ActivatedRoute} from "@angular/router"; +import {VsDuelService} from "../../../services/vs-duel.service"; +import {VsDuelDetailModel} from "../../../models/vsDuel.model"; + + +@Component({ + selector: 'app-vs-duel-detail', + templateUrl: './vs-duel-detail.component.html', + styleUrl: './vs-duel-detail.component.css' +}) +export class VsDuelDetailComponent implements OnInit { + + private readonly _activatedRote: ActivatedRoute = inject(ActivatedRoute); + private readonly _vsDuelService: VsDuelService = inject(VsDuelService); + + public vsDuelId!: string; + public vsDuelDetail: VsDuelDetailModel | undefined; + + ngOnInit() { + this.vsDuelId = this._activatedRote.snapshot.params['id']; + + this.getVsDuelDetail(this.vsDuelId); + } + + getVsDuelDetail(vsDuelId: string) { + this._vsDuelService.getVsDuelDetail(vsDuelId).subscribe({ + next: ((response: VsDuelDetailModel) => { + this.vsDuelDetail = response; + this.vsDuelDetail.vsDuelParticipants = this.vsDuelDetail.vsDuelParticipants.sort((a, b) => b.weeklyPoints - a.weeklyPoints); + }) + }); + } +} diff --git a/Ui/src/app/pages/vs-duel/vs-duel-edit/vs-duel-edit.component.css b/Ui/src/app/pages/vs-duel/vs-duel-edit/vs-duel-edit.component.css new file mode 100644 index 0000000..e69de29 diff --git a/Ui/src/app/pages/vs-duel/vs-duel-edit/vs-duel-edit.component.html b/Ui/src/app/pages/vs-duel/vs-duel-edit/vs-duel-edit.component.html new file mode 100644 index 0000000..d460420 --- /dev/null +++ b/Ui/src/app/pages/vs-duel/vs-duel-edit/vs-duel-edit.component.html @@ -0,0 +1,133 @@ +
+

Edit VS-Duel

+ +
+ +
+ + @if (vsDuelForm) { +
+
+

+ +

+
+
+ +
+
+ + + @if (df['opponentName'].invalid && (df['opponentName'].dirty || df['opponentName'].touched)) { +
+ @if (df['opponentName'].hasError('required')) { +

Opponent Name is required

+ } + @if (df['opponentName'].hasError('maxlength')) { +

Maximum 150 characters allowed

+ } +
+ } +
+
+ + + @if (df['opponentServer'].invalid && (df['opponentServer'].dirty || df['opponentServer'].touched)) { +
+ @if (df['opponentServer'].hasError('required')) { +

Opponent server is required

+ } +
+ } +
+
+ + + @if (df['opponentPower'].invalid && (df['opponentPower'].dirty || df['opponentPower'].touched)) { +
+ @if (df['opponentPower'].hasError('required')) { +

Opponent power is required

+ } +
+ } +
+
+ + + @if (df['opponentSize'].invalid && (df['opponentSize'].dirty || df['opponentSize'].touched)) { +
+ @if (df['opponentSize'].hasError('required')) { +

Opponent size is required

+ } +
+ } +
+
+ + +
+
+
+ +
+
+
+
+
+
+

+ +

+
+
+ +
+ + @for (participant of vsDuelParticipants.controls; track participant; let i=$index) { + +
+ + + @if (participant.get('weeklyPoints')?.invalid && (participant.get('weeklyPoints')?.touched || participant.get('weeklyPoints')?.dirty)) { +
+ @if (participant.get('weeklyPoints')?.hasError('required')){ +

Weekly points is required

+ } + @if (participant.get('weeklyPoints')?.hasError('pattern')){ +

Weekly points must not be less than 0

+ } +
+ } +
+
+ } +
+
+
+ +
+
+
+
+
+
+ } + +
diff --git a/Ui/src/app/pages/vs-duel/vs-duel-edit/vs-duel-edit.component.ts b/Ui/src/app/pages/vs-duel/vs-duel-edit/vs-duel-edit.component.ts new file mode 100644 index 0000000..546c7ed --- /dev/null +++ b/Ui/src/app/pages/vs-duel/vs-duel-edit/vs-duel-edit.component.ts @@ -0,0 +1,126 @@ +import {Component, inject, OnInit} from '@angular/core'; +import {ActivatedRoute} from "@angular/router"; +import {VsDuelService} from "../../../services/vs-duel.service"; +import {VsDuelDetailModel, VsDuelModel} from "../../../models/vsDuel.model"; +import {FormArray, FormControl, FormGroup, Validators} from "@angular/forms"; +import {VsDuelParticipantService} from "../../../services/vs-duel-participant.service"; +import {VsDuelParticipantModel} from "../../../models/vsDuelParticipant.model"; +import {forkJoin, Observable} from "rxjs"; +import {ToastrService} from "ngx-toastr"; + +@Component({ + selector: 'app-vs-duel-edit', + templateUrl: './vs-duel-edit.component.html', + styleUrl: './vs-duel-edit.component.css' +}) +export class VsDuelEditComponent implements OnInit { + + private readonly _activatedRote: ActivatedRoute = inject(ActivatedRoute); + private readonly _vsDuelService: VsDuelService = inject(VsDuelService); + private readonly _vsDuelParticipantService: VsDuelParticipantService = inject(VsDuelParticipantService); + private readonly _toastr: ToastrService = inject(ToastrService); + + private vsDuelId!: string; + private vsDuelDetail!: VsDuelDetailModel; + + public vsDuelParticipantsForm: FormGroup = new FormGroup({}); + public vsDuelForm: FormGroup = new FormGroup({}); + + + get vsDuelParticipants(): FormArray { + return this.vsDuelParticipantsForm.get('vsDuelParticipants') as FormArray; + } + + get df() { + return this.vsDuelForm.controls; + } + + ngOnInit() { + this.vsDuelId = this._activatedRote.snapshot.params['id']; + this.getVsDuelDetail(this.vsDuelId); + } + + getVsDuelDetail(vsDuelId: string) { + this._vsDuelService.getVsDuelDetail(vsDuelId).subscribe({ + next: ((response: VsDuelDetailModel) => { + this.vsDuelDetail = response; + this.vsDuelDetail.vsDuelParticipants = this.vsDuelDetail.vsDuelParticipants.sort((a, b) => a.playerName.localeCompare(b.playerName)); + this.createForm(response); + }) + }); + } + + createForm(vsDuelDetail: VsDuelDetailModel) { + const d = new Date(vsDuelDetail.eventDate); + this.vsDuelForm = new FormGroup({ + id: new FormControl(vsDuelDetail.id), + allianceId: new FormControl(vsDuelDetail.allianceId), + eventDate: new FormControl(new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate())).toISOString().substring(0, 10)), + won: new FormControl(vsDuelDetail.won), + opponentName: new FormControl(vsDuelDetail.opponentName, [Validators.required, Validators.maxLength(150)]), + opponentServer: new FormControl(vsDuelDetail.opponentServer, [Validators.required]), + opponentPower: new FormControl(vsDuelDetail.opponentPower, [Validators.required]), + opponentSize: new FormControl(vsDuelDetail.opponentSize, [Validators.required]), + }); + + this.vsDuelParticipantsForm = new FormGroup({ + vsDuelParticipants: new FormArray([]) + }); + + vsDuelDetail.vsDuelParticipants.forEach((vsDuelParticipant) => { + this.vsDuelParticipants.push(new FormGroup({ + id: new FormControl(vsDuelParticipant.id), + playerId: new FormControl(vsDuelParticipant.playerId), + vsDuelId: new FormControl(vsDuelParticipant.vsDuelId), + weeklyPoints: new FormControl(vsDuelParticipant.weeklyPoints, [Validators.required, Validators.pattern('(0|[1-9]\\d*)')]), + playerName: new FormControl(vsDuelParticipant.playerName), + })); + }) + } + + onUpdatePlayers() { + const requests: Observable[] = []; + + this.vsDuelParticipants.controls.forEach((control) => { + if (control.dirty) { + const vsDuelParticipant: VsDuelParticipantModel = control.value as VsDuelParticipantModel; + const request = this._vsDuelParticipantService.updateVsDuelParticipant(vsDuelParticipant.id, vsDuelParticipant); + requests.push(request); + } + }) + if (requests.length > 0) { + forkJoin(requests).subscribe({ + next: ((response) => { + if (response.length > 0) { + this._toastr.success(`Successfully updated ${response.length} players`); + this.getVsDuelDetail(this.vsDuelId); + } + }), + error: ((error) => { + console.log(error); + this._toastr.error('Unable to update players', 'Update'); + }) + }) + } + } + + onUpdateEvent() { + if (this.vsDuelForm.invalid) { + return; + } + + const vsDuel: VsDuelModel = this.vsDuelForm.value as VsDuelModel; + this._vsDuelService.updateVsDuel(vsDuel.id, vsDuel).subscribe({ + next: ((response) => { + if (response) { + this._toastr.success('Successfully updated event', 'Update'); + this.getVsDuelDetail(response.id); + } + }), + error: ((error) => { + console.log(error); + this._toastr.error('Unable to update event', 'Update'); + }) + }); + } +} diff --git a/Ui/src/app/pages/vs-duel/vs-duel.component.css b/Ui/src/app/pages/vs-duel/vs-duel.component.css new file mode 100644 index 0000000..e69de29 diff --git a/Ui/src/app/pages/vs-duel/vs-duel.component.html b/Ui/src/app/pages/vs-duel/vs-duel.component.html new file mode 100644 index 0000000..b89ef16 --- /dev/null +++ b/Ui/src/app/pages/vs-duel/vs-duel.component.html @@ -0,0 +1,64 @@ +
+

VS - Duel

+ +
+ +
+ + @if (!currentWeekDuelExists) { +
+ +
+ } @else { +
+ +
+ } + + @if (vsDuels.length > 0) { +
+ + + + + + + + + + + + + + + @for (vsDuel of vsDuels | paginate: { itemsPerPage: 10, currentPage: page, id: 'vsDuelTable'}; track vsDuel.id) { + + + + + + + + + + + } + +
YearWeekOpponent nameOpponent serverOpponent powerOpponent sizeWonAction
{{vsDuel.eventDate | date: 'yyyy'}}{{vsDuel.eventDate | week}}{{vsDuel.opponentName}}{{vsDuel.opponentServer}}{{vsDuel.opponentPower}}{{vsDuel.opponentSize}} + + +
+ + + +
+
+ +
+ } + +
diff --git a/Ui/src/app/pages/vs-duel/vs-duel.component.ts b/Ui/src/app/pages/vs-duel/vs-duel.component.ts new file mode 100644 index 0000000..4693943 --- /dev/null +++ b/Ui/src/app/pages/vs-duel/vs-duel.component.ts @@ -0,0 +1,116 @@ +import {Component, inject, OnInit} from '@angular/core'; +import {VsDuelModel} from "../../models/vsDuel.model"; +import {JwtTokenService} from "../../services/jwt-token.service"; +import {NgbModal} from "@ng-bootstrap/ng-bootstrap"; +import {VsDuelCreateModalComponent} from "../../modals/vs-duel-create-modal/vs-duel-create-modal.component"; +import {VsDuelService} from "../../services/vs-duel.service"; +import {WeekPipe} from "../../helpers/week.pipe"; +import Swal from "sweetalert2"; +import {Router} from "@angular/router"; + +@Component({ + selector: 'app-vs-duel', + templateUrl: './vs-duel.component.html', + styleUrl: './vs-duel.component.css', + providers: [WeekPipe] +}) +export class VsDuelComponent implements OnInit { + + private readonly _weekPipe: WeekPipe = new WeekPipe(); + private readonly _tokenService: JwtTokenService = inject(JwtTokenService); + private readonly _modalService : NgbModal = inject(NgbModal); + private readonly _vsDuelService: VsDuelService = inject(VsDuelService); + public readonly _router: Router = inject(Router); + + public currentDate: Date = new Date(); + public vsDuels: VsDuelModel[] = []; + public page: number = 1; + public currentWeekDuelExists: boolean = false; + + ngOnInit() { + this.getVsDuels(this._tokenService.getAllianceId()!, 10); + } + + onCreateEvent() { + const vsDuel: VsDuelModel = { + eventDate: new Date(), + opponentName: '', + allianceId: this._tokenService.getAllianceId()!, + id: '', + won: false, + opponentPower: 0, + opponentServer: 0, + opponentSize: 0, + createdBy: '' + }; + this.openVsDuelEditModal(vsDuel, false); + } + + openVsDuelEditModal(vsDuelModel: VsDuelModel, isUpdate: boolean) { + const modalRef = this._modalService.open(VsDuelCreateModalComponent, + {animation: true, backdrop: 'static', centered: true, size: 'lg'}); + modalRef.componentInstance.vsDuelModel = vsDuelModel; + modalRef.componentInstance.isUpdate = isUpdate; + modalRef.closed.subscribe({ + next: ((response: VsDuelModel) => { + if (response) { + this.getVsDuels(response.allianceId, 10); + } + }) + }) + } + + onGoToVsDuelInformation(vsDuel: VsDuelModel) { + this._router.navigate(['vs-duel-detail', vsDuel.id]).then(); + } + + onEditVsDuel(vsDuel: VsDuelModel) { + this._router.navigate(['vs-duel-edit', vsDuel.id]).then(); + } + + onDeleteVsDuel(vsDuel: VsDuelModel) { + Swal.fire({ + title: "Delete VS-Duel ?", + text: `Do you really want to delete the VS-Duel`, + icon: "warning", + showCancelButton: true, + confirmButtonColor: "#3085d6", + cancelButtonColor: "#d33", + confirmButtonText: "Yes, delete it!" + }).then((result) => { + if (result.isConfirmed) { + this._vsDuelService.deleteVsDuel(vsDuel.id).subscribe({ + next: ((response) => { + if (response) { + Swal.fire({ + title: "Deleted!", + text: "VS-Duel has been deleted", + icon: "success" + }).then(_ => this.getVsDuels(vsDuel.allianceId, 10)); + } + }), + error: (error: Error) => { + console.log(error); + } + }); + } + }); + } + + private getVsDuels(allianceId: string, take: number) { + this.vsDuels = [] + this.currentWeekDuelExists = false; + this._vsDuelService.getAllianceVsDuels(allianceId, take).subscribe({ + next: ((response) => { + if (response) { + response.forEach((vsDuel: VsDuelModel) => { + if (this._weekPipe.transform(vsDuel.eventDate) === this._weekPipe.transform(new Date())) { + this.currentWeekDuelExists = true; + } + }) + this.vsDuels = response; + } + }) + }); + } +} diff --git a/Ui/src/app/services/admonition.service.ts b/Ui/src/app/services/admonition.service.ts new file mode 100644 index 0000000..cc06c3d --- /dev/null +++ b/Ui/src/app/services/admonition.service.ts @@ -0,0 +1,34 @@ +import {inject, Injectable} from '@angular/core'; +import {environment} from "../../environments/environment"; +import {HttpClient} from "@angular/common/http"; +import {Observable} from "rxjs"; +import {AdmonitionModel} from "../models/admonition.model"; + +@Injectable({ + providedIn: 'root' +}) +export class AdmonitionService { + + private readonly _serviceUrl = environment.apiBaseUrl + 'Admonitions/'; + private readonly _httpClient: HttpClient = inject(HttpClient); + + getAdmonition(admonitionId: string): Observable { + return this._httpClient.get(this._serviceUrl + admonitionId); + } + + getPlayerAdmonitions(playerId: string): Observable { + return this._httpClient.get(this._serviceUrl + 'Player/' + playerId); + } + + createAdmonition(admonition: AdmonitionModel): Observable { + return this._httpClient.post(this._serviceUrl, admonition); + } + + updateAdmonition(admonitionId: string, admonition: AdmonitionModel): Observable { + return this._httpClient.put(this._serviceUrl + admonitionId, admonition); + } + + deleteAdmonition(admonitionId: string): Observable { + return this._httpClient.delete(this._serviceUrl + admonitionId); + } +} diff --git a/Ui/src/app/services/alliance.service.ts b/Ui/src/app/services/alliance.service.ts new file mode 100644 index 0000000..44df96a --- /dev/null +++ b/Ui/src/app/services/alliance.service.ts @@ -0,0 +1,22 @@ +import {inject, Injectable} from '@angular/core'; +import {environment} from "../../environments/environment"; +import {HttpClient} from "@angular/common/http"; +import {Observable} from "rxjs"; +import {AllianceModel} from "../models/alliance.model"; + +@Injectable({ + providedIn: 'root' +}) +export class AllianceService { + + private readonly _serviceUrl = environment.apiBaseUrl + 'Alliances/'; + private readonly _httpClient: HttpClient = inject(HttpClient); + + getAlliance(allianceId: string): Observable { + return this._httpClient.get(this._serviceUrl + allianceId); + } + + updateAlliance(allianceId: string, alliance: AllianceModel): Observable { + return this._httpClient.put(this._serviceUrl + allianceId, alliance); + } +} diff --git a/Ui/src/app/services/authentication.service.ts b/Ui/src/app/services/authentication.service.ts new file mode 100644 index 0000000..322507b --- /dev/null +++ b/Ui/src/app/services/authentication.service.ts @@ -0,0 +1,133 @@ +import {inject, Injectable} from '@angular/core'; +import {JwtTokenService} from "./jwt-token.service"; +import {LoggedInUser} from "../models/user.model"; +import {BehaviorSubject, map, Observable} from "rxjs"; +import {Router} from "@angular/router"; +import {DecodedTokenModel} from "../models/decodedToken.model"; +import Swal from "sweetalert2"; +import {LoginRequestModel, LoginResponseModel} from "../models/login.model"; +import {environment} from "../../environments/environment"; +import {HttpClient} from "@angular/common/http"; +import {SignUpRequestModel} from "../models/signUp.model"; +import {ConfirmEmailRequestModel} from "../models/confirmEmailRequest.model"; +import {EmailConfirmationRequestModel} from "../models/emailConfirmationRequest.model"; +import {InviteUserModel} from "../models/inviteUser.model"; +import {RegisterUserModel} from "../models/registerUser.model"; +import {ForgotPasswordModel} from "../models/forgotPassword.model"; +import {ResetPasswordModel} from "../models/resetPassword.model"; + +@Injectable({ + providedIn: 'root' +}) +export class AuthenticationService { + + private readonly _serviceUrl = environment.apiBaseUrl + 'Authentications/'; + private readonly _httpClient: HttpClient = inject(HttpClient); + + private readonly _tokenHelperService: JwtTokenService = inject(JwtTokenService); + private readonly _router: Router = inject(Router); + + private userSubject$: BehaviorSubject = new BehaviorSubject(null); + private tokenExpirationTimer: any; + + get user() { + return this.userSubject$.value; + } + + get authStateChange() : Observable { + return this.userSubject$.asObservable(); + } + + public autoLogin(): void { + const decodedToke: DecodedTokenModel | null = this._tokenHelperService.getDecodedToken(); + + if (decodedToke) { + const expiryDate = new Date(decodedToke.exp * 1000); + if (expiryDate <= new Date) { + this.logout(); + } else { + const loggedInUser: LoggedInUser = this.createLoggedInUser(decodedToke); + this.clearTokenExpirationTimer(); + this.setTokenExpirationTimer(decodedToke.exp); + this.userSubject$.next(loggedInUser); + } + } + } + + public login(loginRequest: LoginRequestModel): Observable { + return this._httpClient.post(this._serviceUrl + 'Login', loginRequest) + .pipe(map((response) => { + if (response.token) { + this._tokenHelperService.setToken(response.token); + const decodedToken: DecodedTokenModel | null = this._tokenHelperService.getDecodedToken(); + if (decodedToken) { + const loggedInUser: LoggedInUser = this.createLoggedInUser(decodedToken); + this.clearTokenExpirationTimer(); + this.setTokenExpirationTimer(decodedToken.exp); + this.userSubject$.next(loggedInUser); + } + } + return response; + })) + } + + public logout(): void { + this._tokenHelperService.removeToken(); + this.userSubject$.next(null); + this._router.navigate(['/login']).then(); + } + + public forgotPassword(forgotPasswordModel: ForgotPasswordModel): Observable { + return this._httpClient.post(this._serviceUrl + 'ForgotPassword', forgotPasswordModel); + } + + public resetPassword(resetPasswordModel: ResetPasswordModel): Observable { + return this._httpClient.post(this._serviceUrl + 'ResetPassword', resetPasswordModel); + } + + public signUp(signUpModel: SignUpRequestModel): Observable { + return this._httpClient.post(this._serviceUrl + 'SignUp', signUpModel); + } + + public registerUser(registerUserModel: RegisterUserModel): Observable { + return this._httpClient.post(this._serviceUrl + 'RegisterUser', registerUserModel); + } + + public confirmEmail(confirmEmailRequest: ConfirmEmailRequestModel): Observable { + return this._httpClient.post(this._serviceUrl + 'ConfirmEmail', confirmEmailRequest) + } + + public resendConfirmationEmail(emailConfirmation: EmailConfirmationRequestModel): Observable { + return this._httpClient.post(this._serviceUrl + 'ResendConfirmationEmail', emailConfirmation) + } + + public inviteUser(inviteUserModel: InviteUserModel): Observable { + return this._httpClient.post(this._serviceUrl + 'InviteUser', inviteUserModel); + } + + private createLoggedInUser(decodedToke: DecodedTokenModel): LoggedInUser { + return { + id: decodedToke.userId, + userName: decodedToke.playerName, + email: decodedToke.email, + allianceId: decodedToke.allianceId, + allianceName: decodedToke.allianceName + }; + } + + private clearTokenExpirationTimer(): void { + if (this.tokenExpirationTimer) { + clearTimeout(this.tokenExpirationTimer); + } + this.tokenExpirationTimer = null; + } + + private setTokenExpirationTimer(exp: number): void { + const duration: number = new Date(exp * 1000).getTime() - new Date().getTime(); + this.tokenExpirationTimer = setTimeout(() => { + Swal.fire('Session expired!', 'The session has expired and you will be logged out automatically', 'info').then(() => { + this.logout(); + }) + }, duration); + } +} diff --git a/Ui/src/app/services/desert-storm-participant.service.ts b/Ui/src/app/services/desert-storm-participant.service.ts new file mode 100644 index 0000000..6b65344 --- /dev/null +++ b/Ui/src/app/services/desert-storm-participant.service.ts @@ -0,0 +1,29 @@ +import {inject, Injectable} from '@angular/core'; +import {environment} from "../../environments/environment"; +import {HttpClient, HttpParams} from "@angular/common/http"; +import {Observable} from "rxjs"; +import {CreateDesertStormParticipantModel, DesertStormParticipantModel} from "../models/desertStormParticipant.model"; + +@Injectable({ + providedIn: 'root' +}) +export class DesertStormParticipantService { + + private readonly _serviceUrl = environment.apiBaseUrl + 'DesertStormParticipants/'; + private readonly _httpClient: HttpClient = inject(HttpClient); + + insertDesertStormOParticipants(createDesertStormParticipants: CreateDesertStormParticipantModel[]): Observable { + return this._httpClient.post(this._serviceUrl, createDesertStormParticipants); + } + + getPlayerDesertStormParticipants(playerId: string, last: number): Observable { + let params = new HttpParams(); + params = params.append('last', last); + return this._httpClient.get(this._serviceUrl + 'Player/' + playerId, {params: params}); + } + + updateDesertStormParticipant(desertStormParticipantId: string, desertStormParticipantModel: DesertStormParticipantModel): Observable { + return this._httpClient.put(this._serviceUrl + desertStormParticipantId, desertStormParticipantModel); + } + +} diff --git a/Ui/src/app/services/desert-storm.service.ts b/Ui/src/app/services/desert-storm.service.ts new file mode 100644 index 0000000..92b969a --- /dev/null +++ b/Ui/src/app/services/desert-storm.service.ts @@ -0,0 +1,36 @@ +import {inject, Injectable} from '@angular/core'; +import {environment} from "../../environments/environment"; +import {HttpClient, HttpParams} from "@angular/common/http"; +import {CreateDesertStormModel, DesertStormDetailModel, DesertStormModel} from "../models/desertStorm.model"; +import {Observable} from "rxjs"; + +@Injectable({ + providedIn: 'root' +}) +export class DesertStormService { + + private readonly _serviceUrl = environment.apiBaseUrl + 'DesertStorms/'; + private readonly _httpClient: HttpClient = inject(HttpClient); + + createDesertStorm(createModel: CreateDesertStormModel): Observable { + return this._httpClient.post(this._serviceUrl, createModel); + } + + getAllianceDesertStorms(allianceId: string, take: number): Observable { + let params = new HttpParams(); + params = params.append('take', take); + return this._httpClient.get(this._serviceUrl + 'Alliance/' + allianceId, {params: params}); + } + + getDesertStormDetail(desertStormId: string): Observable { + return this._httpClient.get(this._serviceUrl + 'GetDesertStormDetail/' + desertStormId); + } + + updateDesertStorm(desertStormId: string, desertStorm: DesertStormModel): Observable { + return this._httpClient.put(this._serviceUrl + desertStormId, desertStorm); + } + + deleteDesertStorm(desertStormId: string): Observable { + return this._httpClient.delete(this._serviceUrl + desertStormId); + } +} diff --git a/Ui/src/app/services/jwt-token.service.ts b/Ui/src/app/services/jwt-token.service.ts new file mode 100644 index 0000000..291baa5 --- /dev/null +++ b/Ui/src/app/services/jwt-token.service.ts @@ -0,0 +1,58 @@ +import {inject, Injectable} from '@angular/core'; +import {JwtHelperService} from "@auth0/angular-jwt"; +import {DecodedTokenModel} from "../models/decodedToken.model"; + +@Injectable({ + providedIn: 'root' +}) +export class JwtTokenService { + + private readonly _tokenHelper: JwtHelperService = inject(JwtHelperService); + private localStorageKey = 'LastWarPlayerManagementToken'; + + public setToken(token: string): void { + localStorage.setItem(this.localStorageKey, token); + } + + public getToken(): string | null { + return localStorage.getItem(this.localStorageKey); + } + + public removeToken(): void { + localStorage.removeItem(this.localStorageKey); + } + + public getAllianceId(): string | null { + const token = localStorage.getItem(this.localStorageKey); + if (!token) { + return null; + } + return this._tokenHelper.decodeToken(token)!.allianceId; + } + + public getUserId(): string | null { + const token = localStorage.getItem(this.localStorageKey); + if (!token) { + return null; + } + return this._tokenHelper.decodeToken(token)!.userId; + } + + public getRole(): string | null { + const token = localStorage.getItem(this.localStorageKey); + if (!token) { + return null; + } + const decodedToken = this._tokenHelper.decodeToken(token)!.userId; + // @ts-ignore + return decodedToken["http://schemas.microsoft.com/ws/2008/06/identity/claims/role"]; + } + + public getDecodedToken(): DecodedTokenModel | null { + const token = localStorage.getItem(this.localStorageKey); + if (!token) { + return null; + } + return this._tokenHelper.decodeToken(token); + } +} diff --git a/Ui/src/app/services/marshal-guard-participant.service.ts b/Ui/src/app/services/marshal-guard-participant.service.ts new file mode 100644 index 0000000..1f857f8 --- /dev/null +++ b/Ui/src/app/services/marshal-guard-participant.service.ts @@ -0,0 +1,32 @@ +import {inject, Injectable} from '@angular/core'; +import {environment} from "../../environments/environment"; +import {HttpClient, HttpParams} from "@angular/common/http"; +import { + CreateMarshalGuardParticipantModel, MarshalGuardParticipantModel, +} from "../models/marshalGuardParticipant.model"; +import {Observable} from "rxjs"; + + +@Injectable({ + providedIn: 'root' +}) +export class MarshalGuardParticipantService { + + private readonly _serviceUrl = environment.apiBaseUrl + 'MarshalGuardParticipants/'; + private readonly _httpClient: HttpClient = inject(HttpClient); + + insertMarshalGuardOParticipants(createMarshalGuardParticipants: CreateMarshalGuardParticipantModel[]) : Observable { + return this._httpClient.post(this._serviceUrl, createMarshalGuardParticipants); + } + + getPlayerMarshalGuardParticipants(playerId: string, last: number): Observable { + let params = new HttpParams(); + params = params.append('last', last); + return this._httpClient.get(this._serviceUrl + 'Player/' + playerId, {params: params}); + } + + updateMarshalGuardParticipant(marshalGuardParticipantId: string, marshalGuardParticipantModel: MarshalGuardParticipantModel) : Observable { + return this._httpClient.put(this._serviceUrl + marshalGuardParticipantId, marshalGuardParticipantModel); + } + +} diff --git a/Ui/src/app/services/marshal-guard.service.ts b/Ui/src/app/services/marshal-guard.service.ts new file mode 100644 index 0000000..e68f0fe --- /dev/null +++ b/Ui/src/app/services/marshal-guard.service.ts @@ -0,0 +1,43 @@ +import {inject, Injectable} from '@angular/core'; +import {environment} from "../../environments/environment"; +import {HttpClient, HttpParams} from "@angular/common/http"; +import { + CreateMarshalGuardModel, + MarshalGuardDetailModel, + MarshalGuardModel, + UpdateMarshalGuardModel +} from "../models/marshalGuard.model"; +import {Observable} from "rxjs"; + +@Injectable({ + providedIn: 'root' +}) +export class MarshalGuardService { + + private readonly _serviceUrl = environment.apiBaseUrl + 'MarshalGuards/'; + private readonly _httpClient: HttpClient = inject(HttpClient); + + + public insertMarshalGuards(createMarshalGuards: CreateMarshalGuardModel): Observable { + return this._httpClient.post(this._serviceUrl, createMarshalGuards); + } + + public getMarshalGuardDetail(marshalGuardId: string): Observable { + return this._httpClient.get(this._serviceUrl + 'GetMarshalGuardDetail/' + marshalGuardId); + } + + public getAllianceMarshalGuards(allianceId: string, take: number): Observable { + let params = new HttpParams(); + params = params.append('take', take); + return this._httpClient.get(this._serviceUrl + 'Alliance/' + allianceId, {params: params}); + } + + public updateMarshalGuard(marshalGuardId: string, marshalGuard: UpdateMarshalGuardModel): Observable { + return this._httpClient.put(this._serviceUrl + marshalGuardId, marshalGuard); + } + + public deleteMarshalGuard(marshalGuardId: string): Observable { + return this._httpClient.delete(this._serviceUrl + marshalGuardId); + } + +} diff --git a/Ui/src/app/services/notes.service.ts b/Ui/src/app/services/notes.service.ts new file mode 100644 index 0000000..48953aa --- /dev/null +++ b/Ui/src/app/services/notes.service.ts @@ -0,0 +1,34 @@ +import {inject, Injectable} from '@angular/core'; +import {environment} from "../../environments/environment"; +import {HttpClient} from "@angular/common/http"; +import {Observable} from "rxjs"; +import {NoteModel} from "../models/note.model"; + +@Injectable({ + providedIn: 'root' +}) +export class NotesService { + + private readonly _serviceUrl = environment.apiBaseUrl + 'Notes/'; + private readonly _httpClient: HttpClient = inject(HttpClient); + + public getNote(notedId: string): Observable { + return this._httpClient.get(this._serviceUrl + notedId); + } + + public getPlayerNotes(playerId: string): Observable { + return this._httpClient.get(this._serviceUrl + 'Player/' + playerId); + } + + public createNote(note: NoteModel): Observable { + return this._httpClient.post(this._serviceUrl, note); + } + + public updateNote(noteId: string, note: NoteModel): Observable { + return this._httpClient.put(this._serviceUrl + noteId, note); + } + + public deleteNote(noteId: string): Observable { + return this._httpClient.delete(this._serviceUrl + noteId); + } +} diff --git a/Ui/src/app/services/player.service.ts b/Ui/src/app/services/player.service.ts new file mode 100644 index 0000000..b7acf7b --- /dev/null +++ b/Ui/src/app/services/player.service.ts @@ -0,0 +1,34 @@ +import {inject, Injectable} from '@angular/core'; +import {environment} from "../../environments/environment"; +import {HttpClient} from "@angular/common/http"; +import {Observable} from "rxjs"; +import {CreatePlayerModel, PlayerModel, UpdatePlayerModel} from "../models/player.model"; + +@Injectable({ + providedIn: 'root' +}) +export class PlayerService { + + private readonly _serviceUrl = environment.apiBaseUrl + 'Players/'; + private readonly _httpClient: HttpClient = inject(HttpClient); + + public getPlayer(playerId: string): Observable { + return this._httpClient.get(this._serviceUrl + playerId); + } + + public getAlliancePlayer(allianceId: string): Observable { + return this._httpClient.get(this._serviceUrl + 'Alliance/' + allianceId); + } + + public updatePlayer(playerId: string, player: UpdatePlayerModel): Observable { + return this._httpClient.put(this._serviceUrl + playerId, player); + } + + public insertPlayer(player: CreatePlayerModel): Observable { + return this._httpClient.post(this._serviceUrl, player); + } + + public deletePlayer(playerId: string): Observable { + return this._httpClient.delete(this._serviceUrl + playerId); + } +} diff --git a/Ui/src/app/services/rank.service.ts b/Ui/src/app/services/rank.service.ts new file mode 100644 index 0000000..461896b --- /dev/null +++ b/Ui/src/app/services/rank.service.ts @@ -0,0 +1,18 @@ +import {inject, Injectable} from '@angular/core'; +import {environment} from "../../environments/environment"; +import {HttpClient} from "@angular/common/http"; +import {Observable} from "rxjs"; +import {RankModel} from "../models/rank.model"; + +@Injectable({ + providedIn: 'root' +}) +export class RankService { + + private readonly _serviceUrl = environment.apiBaseUrl + 'Ranks/'; + private readonly _httpClient: HttpClient = inject(HttpClient); + + getRanks(): Observable { + return this._httpClient.get(this._serviceUrl); + } +} diff --git a/Ui/src/app/services/spinner.service.ts b/Ui/src/app/services/spinner.service.ts new file mode 100644 index 0000000..961fcc5 --- /dev/null +++ b/Ui/src/app/services/spinner.service.ts @@ -0,0 +1,29 @@ +import {inject, Injectable} from '@angular/core'; +import {NgxSpinnerService} from "ngx-spinner"; + +@Injectable({ + providedIn: 'root' +}) +export class SpinnerService { + + private busyRequestCount: number = 0; + + private readonly _ngxSpinnerService: NgxSpinnerService = inject(NgxSpinnerService); + + busy(): void { + this.busyRequestCount++; + this._ngxSpinnerService.show(undefined, { + type: 'ball-8bits', + bdColor: 'rgba(0, 0, 0, 0.8)', + color: '#87aff1' + }).then(); + } + + idle(): void { + this.busyRequestCount--; + if (this.busyRequestCount <= 0) { + this.busyRequestCount = 0; + this._ngxSpinnerService.hide().then(); + } + } +} diff --git a/Ui/src/app/services/user.service.ts b/Ui/src/app/services/user.service.ts new file mode 100644 index 0000000..e22e968 --- /dev/null +++ b/Ui/src/app/services/user.service.ts @@ -0,0 +1,34 @@ +import {inject, Injectable} from '@angular/core'; +import {environment} from "../../environments/environment"; +import {HttpClient} from "@angular/common/http"; +import {Observable} from "rxjs"; +import {UpdateUserModel, UserModel} from "../models/user.model"; +import {ChangePasswordModel} from "../models/changePassword.model"; + +@Injectable({ + providedIn: 'root' +}) +export class UserService { + + private readonly _serviceUrl = environment.apiBaseUrl + 'Users/'; + private readonly _httpClient: HttpClient = inject(HttpClient); + + getUser(userId: string): Observable { + return this._httpClient.get(this._serviceUrl + userId); + } + getAllianceUsers(allianceId: string): Observable { + return this._httpClient.get(this._serviceUrl + 'Alliance/' + allianceId); + } + + updateUser(userId: string, updateUser: UpdateUserModel): Observable { + return this._httpClient.put(this._serviceUrl + userId, updateUser); + } + + changeUserPassword(changeUserPasswordModel: ChangePasswordModel): Observable { + return this._httpClient.post(this._serviceUrl + 'ChangeUserPassword', changeUserPasswordModel); + } + + deleteUser(userId: string): Observable { + return this._httpClient.delete(this._serviceUrl + userId); + } +} diff --git a/Ui/src/app/services/vs-duel-participant.service.ts b/Ui/src/app/services/vs-duel-participant.service.ts new file mode 100644 index 0000000..67cccd7 --- /dev/null +++ b/Ui/src/app/services/vs-duel-participant.service.ts @@ -0,0 +1,19 @@ +import {inject, Injectable} from '@angular/core'; +import {environment} from "../../environments/environment"; +import {HttpClient} from "@angular/common/http"; +import {VsDuelParticipantModel} from "../models/vsDuelParticipant.model"; +import {Observable} from "rxjs"; + +@Injectable({ + providedIn: 'root' +}) +export class VsDuelParticipantService { + + private readonly _serviceUrl = environment.apiBaseUrl + 'VsDuelParticipants/'; + private readonly _httpClient: HttpClient = inject(HttpClient); + + updateVsDuelParticipant(vsDuelParticipantId: string, vsDuelParticipant: VsDuelParticipantModel): Observable { + return this._httpClient.put(this._serviceUrl + vsDuelParticipantId, vsDuelParticipant); + } + +} diff --git a/Ui/src/app/services/vs-duel.service.ts b/Ui/src/app/services/vs-duel.service.ts new file mode 100644 index 0000000..b8b21aa --- /dev/null +++ b/Ui/src/app/services/vs-duel.service.ts @@ -0,0 +1,40 @@ +import {inject, Injectable} from '@angular/core'; +import {environment} from "../../environments/environment"; +import {HttpClient, HttpParams} from "@angular/common/http"; +import {Observable} from "rxjs"; +import {VsDuelDetailModel, VsDuelModel} from "../models/vsDuel.model"; + +@Injectable({ + providedIn: 'root' +}) +export class VsDuelService { + + private readonly _serviceUrl = environment.apiBaseUrl + 'VsDuels/'; + private readonly _httpClient: HttpClient = inject(HttpClient); + + public getVsDuel(vsDuelId: string): Observable { + return this._httpClient.get(this._serviceUrl + vsDuelId); + } + + public getAllianceVsDuels(allianceId: string, take: number): Observable { + let params = new HttpParams(); + params = params.append('take', take); + return this._httpClient.get(this._serviceUrl + 'Alliance/' + allianceId, {params: params}); + } + + public getVsDuelDetail(vsDuelId: string): Observable { + return this._httpClient.get(this._serviceUrl + 'GetDetailVsDuel/' + vsDuelId); + } + + public createVsDuel(vsDuel: VsDuelModel): Observable { + return this._httpClient.post(this._serviceUrl, vsDuel); + } + + public updateVsDuel(vsDuelId: string, vsDuel: VsDuelModel): Observable { + return this._httpClient.put(this._serviceUrl + vsDuelId, vsDuel); + } + + public deleteVsDuel(vsDuelId: string): Observable { + return this._httpClient.delete(this._serviceUrl + vsDuelId); + } +} diff --git a/Ui/src/assets/images/background.jpg b/Ui/src/assets/images/background.jpg new file mode 100644 index 0000000..77c642a Binary files /dev/null and b/Ui/src/assets/images/background.jpg differ diff --git a/Ui/src/index.html b/Ui/src/index.html index e4a618e..8436eba 100644 --- a/Ui/src/index.html +++ b/Ui/src/index.html @@ -2,10 +2,11 @@ - Ui + Last War - Player Management + diff --git a/Ui/src/main.ts b/Ui/src/main.ts index c58dc05..be6bfab 100644 --- a/Ui/src/main.ts +++ b/Ui/src/main.ts @@ -1,3 +1,5 @@ +/// + import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; diff --git a/Ui/src/styles.css b/Ui/src/styles.css index c4bb1cb..01aa15c 100644 --- a/Ui/src/styles.css +++ b/Ui/src/styles.css @@ -1,6 +1,31 @@ +@import url("https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"); + html, body { width: 100%; height: 100%; margin: 0; font-family: 'Poppins', sans-serif; } + +.custom-edit-icon { + cursor: pointer; + color: blueviolet; +} + +.custom-delete-icon { + cursor: pointer; + color: orangered; +} + +.custom-info-icon { + cursor: pointer; + color: #2ea805; +} + +.custom-pagination .ngx-pagination li a{ + color: white; +} + +.custom-pagination .ngx-pagination li a:hover{ + color: black; +} diff --git a/Ui/tsconfig.app.json b/Ui/tsconfig.app.json index 374cc9d..ec26f70 100644 --- a/Ui/tsconfig.app.json +++ b/Ui/tsconfig.app.json @@ -3,7 +3,9 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "./out-tsc/app", - "types": [] + "types": [ + "@angular/localize" + ] }, "files": [ "src/main.ts" diff --git a/Ui/tsconfig.spec.json b/Ui/tsconfig.spec.json index be7e9da..c63b698 100644 --- a/Ui/tsconfig.spec.json +++ b/Ui/tsconfig.spec.json @@ -4,7 +4,8 @@ "compilerOptions": { "outDir": "./out-tsc/spec", "types": [ - "jasmine" + "jasmine", + "@angular/localize" ] }, "include": [ diff --git a/Utilities/Classes/EmailConfiguration.cs b/Utilities/Classes/EmailConfiguration.cs new file mode 100644 index 0000000..562a8bb --- /dev/null +++ b/Utilities/Classes/EmailConfiguration.cs @@ -0,0 +1,18 @@ +using System.ComponentModel.DataAnnotations; + +namespace Utilities.Classes; + +public class EmailConfiguration +{ + [Required] public required string FromAddress { get; set; } + + [Required] public required string DisplayName { get; set; } + + [Required] public required string SmtpServer { get; set; } + + [Range(1, 65535)] public int Port { get; set; } + + [Required] public required string UserName { get; set; } + + [Required] public required string Password { get; set; } +} \ No newline at end of file diff --git a/Utilities/Classes/EmailMessage.cs b/Utilities/Classes/EmailMessage.cs new file mode 100644 index 0000000..08a6c39 --- /dev/null +++ b/Utilities/Classes/EmailMessage.cs @@ -0,0 +1,24 @@ +using Microsoft.AspNetCore.Http; +using MimeKit; + +namespace Utilities.Classes; + +public class EmailMessage +{ + public EmailMessage(IEnumerable to, string subject, string content, IFormFileCollection? attachments = null) + { + To = []; + To.AddRange(to.Select(mail => new MailboxAddress(name: mail, address: mail))); + Subject = subject; + Content = content; + Attachments = attachments; + } + + public List To { get; set; } + + public string Subject { get; set; } + + public string Content { get; set; } + + public IFormFileCollection? Attachments { get; set; } +} \ No newline at end of file diff --git a/Utilities/Interfaces/IEmailService.cs b/Utilities/Interfaces/IEmailService.cs new file mode 100644 index 0000000..2d6cd46 --- /dev/null +++ b/Utilities/Interfaces/IEmailService.cs @@ -0,0 +1,8 @@ +using Utilities.Classes; + +namespace Utilities.Interfaces; + +public interface IEmailService +{ + Task SendEmailAsync(EmailMessage emailMessage); +} \ No newline at end of file diff --git a/Utilities/Services/EmailService.cs b/Utilities/Services/EmailService.cs new file mode 100644 index 0000000..4c37e35 --- /dev/null +++ b/Utilities/Services/EmailService.cs @@ -0,0 +1,74 @@ +using MailKit.Net.Smtp; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using MimeKit; +using Utilities.Classes; +using Utilities.Interfaces; + +namespace Utilities.Services; + +public class EmailService(IOptions emailConfigurationOptions, ILogger logger) + : IEmailService +{ + private readonly EmailConfiguration _emailConfiguration = emailConfigurationOptions.Value; + + public async Task SendEmailAsync(EmailMessage emailMessage) + { + var mimeMessage = await CreateMailMessage(emailMessage); + return await SendAsync(mimeMessage); + } + + private async Task SendAsync(MimeMessage mimeMessage) + { + using var client = new SmtpClient(); + + try + { + await client.ConnectAsync(_emailConfiguration.SmtpServer, _emailConfiguration.Port, true); + + client.AuthenticationMechanisms.Remove("XOAUTH2"); + + await client.AuthenticateAsync(_emailConfiguration.UserName, _emailConfiguration.Password); + + await client.SendAsync(mimeMessage); + + return true; + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return false; + } + } + + private async Task CreateMailMessage(EmailMessage emailMessage) + { + var mimeMessage = new MimeMessage(); + + mimeMessage.From.Add(new MailboxAddress(_emailConfiguration.DisplayName, _emailConfiguration.FromAddress)); + + mimeMessage.To.AddRange(emailMessage.To); + + mimeMessage.Subject = emailMessage.Subject; + + var bodyBuilder = new BodyBuilder + { + HtmlBody = emailMessage.Content + }; + + if (emailMessage.Attachments is not null && emailMessage.Attachments.Any()) + { + foreach (var attachment in emailMessage.Attachments) + { + using var memoryStream = new MemoryStream(); + await attachment.CopyToAsync(memoryStream); + var fileBytes = memoryStream.ToArray(); + + bodyBuilder.Attachments.Add(attachment.FileName, fileBytes, ContentType.Parse(attachment.ContentType)); + } + } + + mimeMessage.Body = bodyBuilder.ToMessageBody(); + return mimeMessage; + } +} \ No newline at end of file diff --git a/Utilities/Utilities.csproj b/Utilities/Utilities.csproj index 886b95f..631100d 100644 --- a/Utilities/Utilities.csproj +++ b/Utilities/Utilities.csproj @@ -1,13 +1,21 @@  - net8.0 + net9.0 enable enable + + + + + + + +