From 35e83c473565a5f053d9e77549df9be90e085b6f Mon Sep 17 00:00:00 2001 From: Tomasi - Developing Date: Tue, 26 Nov 2024 08:43:01 +0100 Subject: [PATCH] beta 0.0.3 --- .gitignore | 2 + Api/.config/dotnet-tools.json | 13 + Api/Api.csproj | 15 +- Api/Controllers/v1/AdmonitionsController.cs | 8 +- Api/Controllers/v1/AlliancesController.cs | 26 +- .../v1/AuthenticationsController.cs | 144 ++- Api/Controllers/v1/CustomEventsController.cs | 143 +++ .../v1/DesertStormParticipantsController.cs | 111 ++ Api/Controllers/v1/DesertStormsController.cs | 47 +- .../v1/MarshalGuardParticipantsController.cs | 106 ++ Api/Controllers/v1/MarshalGuardsController.cs | 52 +- Api/Controllers/v1/NotesController.cs | 9 +- Api/Controllers/v1/PlayersController.cs | 9 +- Api/Controllers/v1/RanksController.cs | 35 + Api/Controllers/v1/UsersController.cs | 119 ++ .../v1/VsDuelParticipantsController.cs | 40 + Api/Controllers/v1/VsDuelsController.cs | 41 +- Api/Program.cs | 10 + Application/Application.csproj | 7 +- Application/ApplicationDependencyInjection.cs | 11 + Application/Classes/EmailContent.cs | 8 + .../Admonition/AdmonitionDto.cs | 8 + .../Admonition/UpdateAdmonitionDto.cs | 3 + .../Alliance/AllianceDto.cs | 10 +- .../Alliance/CreateAllianceDto.cs | 17 - .../Alliance/UpdateAllianceDto.cs | 3 + .../Authentication/ConfirmEmailRequestDto.cs | 13 + .../EmailConfirmationRequestDto.cs | 13 + .../Authentication/ForgotPasswordDto.cs | 13 + .../Authentication/InviteUserDto.cs | 23 + .../Authentication/RegisterUserDto.cs | 26 + .../Authentication/ResetPasswordDto.cs | 19 + ...isterRequestDto.cs => SignUpRequestDto.cs} | 5 +- .../CustomEvent/CreateCustomEventDto.cs | 26 + .../CustomEvent/CustomEventDetailDto.cs | 8 + .../CustomEvent/CustomEventDto.cs | 25 + .../CustomEvent/UpdateCustomEventDto.cs | 27 + .../CustomEventParticipantDto.cs | 16 + .../DesertStorm/CreateDesertStormDto.cs | 23 +- .../DesertStorm/DesertStormDetailDto.cs | 8 + .../DesertStorm/DesertStormDto.cs | 20 +- .../DesertStorm/UpdateDesertStormDto.cs | 19 +- .../CreateDesertStormParticipantDto.cs | 21 + .../DesertStormParticipantDto.cs | 18 + .../UpdateDesertStormParticipantDto.cs | 24 + .../MarshalGuard/CreateMarshalGuardDto.cs | 13 +- .../MarshalGuard/MarshalGuardDetailDto.cs | 8 + .../MarshalGuard/MarshalGuardDto.cs | 18 +- .../MarshalGuard/UpdateMarshalGuardDto.cs | 10 +- .../CreateMarshalGuardParticipantDto.cs | 10 + .../MarshalGuardParticipantDto.cs | 14 + .../UpdateMarshalGuardParticipantDto.cs | 18 + .../DataTransferObjects/Note/CreateNoteDto.cs | 2 + .../DataTransferObjects/Note/NoteDto.cs | 10 + .../Player/CreatePlayerDto.cs | 3 +- .../DataTransferObjects/Player/PlayerDto.cs | 22 +- .../Player/UpdatePlayerDto.cs | 3 +- .../DataTransferObjects/Rank/RankDto.cs | 8 + .../User/ChangePasswordDto.cs | 18 + .../DataTransferObjects/User/UpdateUserDto.cs | 16 + .../DataTransferObjects/User/UserDto.cs | 14 + .../VsDuel/CreateVsDuelDto.cs | 22 +- .../VsDuel/UpdateVsDuelDto.cs | 18 +- .../VsDuel/VsDuelDetailDto.cs | 8 + .../DataTransferObjects/VsDuel/VsDuelDto.cs | 24 +- .../VsDuelParticipant/VsDuelParticipantDto.cs | 14 + Application/Errors/AuthenticationErrors.cs | 9 + Application/Errors/CustomEventErrors.cs | 9 + Application/Errors/RoleErrors.cs | 7 + Application/Errors/UserErrors.cs | 15 + Application/Errors/VsDuelParticipantErrors.cs | 9 + .../Helpers/Email/EmailTemplateFactory.cs | 18 + .../Helpers/Email/EnglishEmailTemplate.cs | 301 +++++ .../Helpers/Email/FrenchEmailTemplate.cs | 165 +++ .../Helpers/Email/GermanEmailTemplate.cs | 300 +++++ .../Helpers/Email/ItalianEmailTemplate.cs | 165 +++ Application/Helpers/HttpExtensions.cs | 22 + .../Interfaces/IAdmonitionRepository.cs | 4 +- Application/Interfaces/IAllianceRepository.cs | 4 +- .../Interfaces/IAuthenticationRepository.cs | 15 +- Application/Interfaces/IClaimTypeService.cs | 8 + .../Interfaces/ICustomEventRepository.cs | 21 + .../IDesertStormParticipantRepository.cs | 17 + .../Interfaces/IDesertStormRepository.cs | 8 +- Application/Interfaces/IEmailTemplate.cs | 14 + .../IMarshalGuardParticipantRepository.cs | 17 + .../Interfaces/IMarshalGuardRepository.cs | 8 +- Application/Interfaces/INoteRepository.cs | 4 +- Application/Interfaces/IPlayerRepository.cs | 4 +- Application/Interfaces/IRankRepository.cs | 9 + Application/Interfaces/IUserRepository.cs | 17 + .../IVsDuelParticipantRepository.cs | 9 + Application/Interfaces/IVsDuelRepository.cs | 8 +- Application/Profiles/AdmonitionProfile.cs | 7 +- Application/Profiles/AllianceProfile.cs | 9 +- Application/Profiles/AuthenticationProfile.cs | 7 +- .../Profiles/CustomEventParticipantProfile.cs | 15 + Application/Profiles/CustomEventProfile.cs | 21 + .../Profiles/DesertStormParticipantProfile.cs | 19 + Application/Profiles/DesertStormProfile.cs | 16 +- .../MarshalGuardParticipantProfile.cs | 19 + Application/Profiles/MarshalGuardProfile.cs | 16 +- Application/Profiles/NoteProfile.cs | 7 +- Application/Profiles/PlayerProfile.cs | 9 +- Application/Profiles/RankProfile.cs | 13 + .../Profiles/VsDuelParticipantProfile.cs | 16 + Application/Profiles/VsDuelProfile.cs | 10 +- .../Repositories/AdmonitionRepository.cs | 7 +- .../Repositories/AllianceRepository.cs | 13 +- .../Repositories/AuthenticationRepository.cs | 214 +++- .../Repositories/CustomEventRepository.cs | 119 ++ .../DesertStormParticipantRepository.cs | 83 ++ .../Repositories/DesertStormRepository.cs | 28 +- .../MarshalGuardParticipantRepository.cs | 83 ++ .../Repositories/MarshalGuardRepository.cs | 33 +- Application/Repositories/NoteRepository.cs | 7 +- Application/Repositories/PlayerRepository.cs | 30 +- Application/Repositories/RankRepository.cs | 23 + Application/Repositories/UserRepository.cs | 143 +++ .../VsDuelParticipantRepository.cs | 34 + Application/Repositories/VsDuelRepository.cs | 57 +- Application/Services/ClaimTypeService.cs | 14 + Application/Services/JwtService.cs | 8 +- Database/ApplicationContext.cs | 18 + .../Configurations/AdmonitionConfiguration.cs | 5 + .../Configurations/AllianceConfiguration.cs | 4 + .../CustomEventConfiguration.cs | 28 + .../CustomEventParticipantConfiguration.cs | 29 + .../DesertStormConfiguration.cs | 18 +- .../DesertStormParticipantConfiguration.cs | 30 + .../MarshalGuardConfiguration.cs | 17 +- .../MarshalGuardParticipantConfiguration.cs | 28 + Database/Configurations/NoteConfiguration.cs | 5 + .../Configurations/PlayerConfiguration.cs | 27 +- .../Configurations/VsDuelConfiguration.cs | 18 +- .../VsDuelParticipantConfiguration.cs | 28 + Database/Database.csproj | 12 +- Database/DatabaseDependencyInjection.cs | 3 +- Database/Entities/Admonition.cs | 10 +- Database/Entities/Alliance.cs | 14 + Database/Entities/CustomEvent.cs | 26 + Database/Entities/CustomEventParticipant.cs | 16 + Database/Entities/DesertStorm.cs | 21 +- Database/Entities/DesertStormParticipant.cs | 18 + Database/Entities/MarshalGuard.cs | 20 +- Database/Entities/MarshalGuardParticipant.cs | 15 + Database/Entities/Note.cs | 10 +- Database/Entities/Player.cs | 23 +- Database/Entities/User.cs | 2 +- Database/Entities/VsDuel.cs | 23 +- Database/Entities/VsDuelParticipant.cs | 14 + ...20241001064648_FixAllianceConfiguration.cs | 58 - ...ner.cs => 20241113124240_Init.Designer.cs} | 441 ++++++- ...0140736_Init.cs => 20241113124240_Init.cs} | 322 +++++- ...0241114100107_ChangeIntToLong.Designer.cs} | 449 +++++++- .../20241114100107_ChangeIntToLong.cs | 123 ++ ...erFlagToDesertStormParticipant.Designer.cs | 1026 +++++++++++++++++ ...StartPlayerFlagToDesertStormParticipant.cs | 98 ++ .../ApplicationContextModelSnapshot.cs | 440 ++++++- Ui/angular.json | 23 +- Ui/package-lock.json | 212 +++- Ui/package.json | 12 +- .../email-confirmation.component.css | 0 .../email-confirmation.component.html | 58 + .../email-confirmation.component.ts | 80 ++ .../forgot-password.component.css | 0 .../forgot-password.component.html | 60 + .../forgot-password.component.ts | 52 + .../Authentication/login/login.component.css | 131 +++ .../Authentication/login/login.component.html | 82 ++ .../Authentication/login/login.component.ts | 71 ++ .../register/register.component.css | 0 .../register/register.component.html | 149 +++ .../register/register.component.ts | 84 ++ .../reset-password.component.css | 31 + .../reset-password.component.html | 107 ++ .../reset-password.component.ts | 87 ++ .../sign-up/sign-up.component.css | 0 .../sign-up/sign-up.component.html | 176 +++ .../sign-up/sign-up.component.ts | 80 ++ Ui/src/app/app-routing.module.ts | 41 +- Ui/src/app/app.component.html | 7 +- Ui/src/app/app.component.ts | 13 +- Ui/src/app/app.module.ts | 99 +- Ui/src/app/guards/auth.guard.ts | 18 + Ui/src/app/helpers/constants.ts | 3 + Ui/src/app/helpers/days.ts | 18 + Ui/src/app/helpers/months.ts | 23 + Ui/src/app/helpers/passwordValidators.ts | 49 + .../under-development.component.css | 0 .../under-development.component.html | 14 + .../under-development.component.ts | 10 + Ui/src/app/helpers/week.pipe.ts | 16 + Ui/src/app/interceptors/jwt.interceptor.ts | 19 + .../app/interceptors/spinner.interceptor.ts | 16 + ...ert-storm-participants-modal.component.css | 6 + ...rt-storm-participants-modal.component.html | 39 + ...sert-storm-participants-modal.component.ts | 56 + .../invite-user-modal.component.css | 0 .../invite-user-modal.component.html | 53 + .../invite-user-modal.component.ts | 61 + .../marshal-guard-modal.component.css | 14 + .../marshal-guard-modal.component.html | 25 + .../marshal-guard-modal.component.ts | 42 + .../player-admonition-modal.component.css | 0 .../player-admonition-modal.component.html | 81 ++ .../player-admonition-modal.component.ts | 155 +++ .../player-edit-modal.component.css | 0 .../player-edit-modal.component.html | 66 ++ .../player-edit-modal.component.ts | 95 ++ .../player-note-modal.component.css | 0 .../player-note-modal.component.html | 82 ++ .../player-note-modal.component.ts | 153 +++ .../user-edit-modal.component.css | 0 .../user-edit-modal.component.html | 68 ++ .../user-edit-modal.component.ts | 56 + .../vs-duel-create-modal.component.css | 0 .../vs-duel-create-modal.component.html | 83 ++ .../vs-duel-create-modal.component.ts | 82 ++ Ui/src/app/models/admonition.model.ts | 9 + Ui/src/app/models/alliance.model.ts | 9 + Ui/src/app/models/changePassword.model.ts | 6 + .../app/models/confirmEmailRequest.model.ts | 4 + Ui/src/app/models/customEvent.model.ts | 15 + .../models/customEventParticipant.model.ts | 8 + Ui/src/app/models/decodedToken.model.ts | 9 + Ui/src/app/models/desertStorm.model.ts | 28 + .../models/desertStormParticipant.model.ts | 17 + .../models/emailConfirmationRequest.model.ts | 4 + Ui/src/app/models/forgotPassword.model.ts | 4 + Ui/src/app/models/inviteUser.model.ts | 7 + Ui/src/app/models/login.model.ts | 8 + Ui/src/app/models/marshalGuard.model.ts | 35 + .../models/marshalGuardParticipant.model.ts | 13 + Ui/src/app/models/note.model.ts | 9 + Ui/src/app/models/player.model.ts | 28 + Ui/src/app/models/rank.model.ts | 4 + Ui/src/app/models/registerUser.model.ts | 8 + Ui/src/app/models/resetPassword.model.ts | 6 + Ui/src/app/models/signUp.model.ts | 9 + Ui/src/app/models/user.model.ts | 20 + Ui/src/app/models/vsDuel.model.ts | 19 + Ui/src/app/models/vsDuelParticipant.model.ts | 7 + .../app/navigation/navigation.component.css | 9 + .../app/navigation/navigation.component.html | 86 ++ Ui/src/app/navigation/navigation.component.ts | 44 + .../app/pages/account/account.component.css | 0 .../app/pages/account/account.component.html | 44 + Ui/src/app/pages/account/account.component.ts | 94 ++ .../app/pages/alliance/alliance.component.css | 0 .../pages/alliance/alliance.component.html | 118 ++ .../app/pages/alliance/alliance.component.ts | 162 +++ .../change-password.component.css | 0 .../change-password.component.html | 129 +++ .../change-password.component.ts | 64 + .../custom-event/custom-event.component.css | 0 .../custom-event/custom-event.component.html | 6 + .../custom-event/custom-event.component.ts | 10 + .../desert-storm-detail.component.css | 3 + .../desert-storm-detail.component.html | 65 ++ .../desert-storm-detail.component.ts | 49 + .../desert-storm/desert-storm.component.css | 0 .../desert-storm/desert-storm.component.html | 148 +++ .../desert-storm/desert-storm.component.ts | 254 ++++ .../marshal-guard-detail.component.css | 14 + .../marshal-guard-detail.component.html | 48 + .../marshal-guard-detail.component.ts | 35 + .../marshal-guard/marshal-guard.component.css | 29 + .../marshal-guard.component.html | 124 ++ .../marshal-guard/marshal-guard.component.ts | 306 +++++ .../player-info-custom-event.component.css | 0 .../player-info-custom-event.component.html | 1 + .../player-info-custom-event.component.ts | 10 + .../player-info-desert-storm.component.css | 0 .../player-info-desert-storm.component.html | 1 + .../player-info-desert-storm.component.ts | 10 + .../player-info-marshal-guard.component.css | 0 .../player-info-marshal-guard.component.html | 19 + .../player-info-marshal-guard.component.ts | 67 ++ .../player-info-vs-duel.component.css | 0 .../player-info-vs-duel.component.html | 1 + .../player-info-vs-duel.component.ts | 10 + .../player-information.component.css | 0 .../player-information.component.html | 91 ++ .../player-information.component.ts | 68 ++ Ui/src/app/pages/player/player.component.css | 0 Ui/src/app/pages/player/player.component.html | 85 ++ Ui/src/app/pages/player/player.component.ts | 198 ++++ .../vs-duel-detail.component.css | 1 + .../vs-duel-detail.component.html | 41 + .../vs-duel-detail.component.ts | 34 + .../vs-duel-edit/vs-duel-edit.component.css | 0 .../vs-duel-edit/vs-duel-edit.component.html | 133 +++ .../vs-duel-edit/vs-duel-edit.component.ts | 126 ++ .../app/pages/vs-duel/vs-duel.component.css | 0 .../app/pages/vs-duel/vs-duel.component.html | 64 + Ui/src/app/pages/vs-duel/vs-duel.component.ts | 116 ++ Ui/src/app/services/admonition.service.ts | 34 + Ui/src/app/services/alliance.service.ts | 22 + Ui/src/app/services/authentication.service.ts | 133 +++ .../desert-storm-participant.service.ts | 29 + Ui/src/app/services/desert-storm.service.ts | 36 + Ui/src/app/services/jwt-token.service.ts | 58 + .../marshal-guard-participant.service.ts | 32 + Ui/src/app/services/marshal-guard.service.ts | 43 + Ui/src/app/services/notes.service.ts | 34 + Ui/src/app/services/player.service.ts | 34 + Ui/src/app/services/rank.service.ts | 18 + Ui/src/app/services/spinner.service.ts | 29 + Ui/src/app/services/user.service.ts | 34 + .../services/vs-duel-participant.service.ts | 19 + Ui/src/app/services/vs-duel.service.ts | 40 + Ui/src/assets/images/background.jpg | Bin 0 -> 44243 bytes Ui/src/index.html | 3 +- Ui/src/main.ts | 2 + Ui/src/styles.css | 25 + Ui/tsconfig.app.json | 4 +- Ui/tsconfig.spec.json | 3 +- Utilities/Classes/EmailConfiguration.cs | 18 + Utilities/Classes/EmailMessage.cs | 24 + Utilities/Interfaces/IEmailService.cs | 8 + Utilities/Services/EmailService.cs | 74 ++ Utilities/Utilities.csproj | 10 +- 323 files changed, 13779 insertions(+), 571 deletions(-) create mode 100644 Api/.config/dotnet-tools.json create mode 100644 Api/Controllers/v1/CustomEventsController.cs create mode 100644 Api/Controllers/v1/DesertStormParticipantsController.cs create mode 100644 Api/Controllers/v1/MarshalGuardParticipantsController.cs create mode 100644 Api/Controllers/v1/RanksController.cs create mode 100644 Api/Controllers/v1/UsersController.cs create mode 100644 Api/Controllers/v1/VsDuelParticipantsController.cs create mode 100644 Application/Classes/EmailContent.cs delete mode 100644 Application/DataTransferObjects/Alliance/CreateAllianceDto.cs create mode 100644 Application/DataTransferObjects/Authentication/ConfirmEmailRequestDto.cs create mode 100644 Application/DataTransferObjects/Authentication/EmailConfirmationRequestDto.cs create mode 100644 Application/DataTransferObjects/Authentication/ForgotPasswordDto.cs create mode 100644 Application/DataTransferObjects/Authentication/InviteUserDto.cs create mode 100644 Application/DataTransferObjects/Authentication/RegisterUserDto.cs create mode 100644 Application/DataTransferObjects/Authentication/ResetPasswordDto.cs rename Application/DataTransferObjects/Authentication/{RegisterRequestDto.cs => SignUpRequestDto.cs} (85%) create mode 100644 Application/DataTransferObjects/CustomEvent/CreateCustomEventDto.cs create mode 100644 Application/DataTransferObjects/CustomEvent/CustomEventDetailDto.cs create mode 100644 Application/DataTransferObjects/CustomEvent/CustomEventDto.cs create mode 100644 Application/DataTransferObjects/CustomEvent/UpdateCustomEventDto.cs create mode 100644 Application/DataTransferObjects/CustomEventParticipant/CustomEventParticipantDto.cs create mode 100644 Application/DataTransferObjects/DesertStorm/DesertStormDetailDto.cs create mode 100644 Application/DataTransferObjects/DesertStormParticipants/CreateDesertStormParticipantDto.cs create mode 100644 Application/DataTransferObjects/DesertStormParticipants/DesertStormParticipantDto.cs create mode 100644 Application/DataTransferObjects/DesertStormParticipants/UpdateDesertStormParticipantDto.cs create mode 100644 Application/DataTransferObjects/MarshalGuard/MarshalGuardDetailDto.cs create mode 100644 Application/DataTransferObjects/MarshalGuardParticipant/CreateMarshalGuardParticipantDto.cs create mode 100644 Application/DataTransferObjects/MarshalGuardParticipant/MarshalGuardParticipantDto.cs create mode 100644 Application/DataTransferObjects/MarshalGuardParticipant/UpdateMarshalGuardParticipantDto.cs create mode 100644 Application/DataTransferObjects/Rank/RankDto.cs create mode 100644 Application/DataTransferObjects/User/ChangePasswordDto.cs create mode 100644 Application/DataTransferObjects/User/UpdateUserDto.cs create mode 100644 Application/DataTransferObjects/User/UserDto.cs create mode 100644 Application/DataTransferObjects/VsDuel/VsDuelDetailDto.cs create mode 100644 Application/DataTransferObjects/VsDuelParticipant/VsDuelParticipantDto.cs create mode 100644 Application/Errors/CustomEventErrors.cs create mode 100644 Application/Errors/RoleErrors.cs create mode 100644 Application/Errors/UserErrors.cs create mode 100644 Application/Errors/VsDuelParticipantErrors.cs create mode 100644 Application/Helpers/Email/EmailTemplateFactory.cs create mode 100644 Application/Helpers/Email/EnglishEmailTemplate.cs create mode 100644 Application/Helpers/Email/FrenchEmailTemplate.cs create mode 100644 Application/Helpers/Email/GermanEmailTemplate.cs create mode 100644 Application/Helpers/Email/ItalianEmailTemplate.cs create mode 100644 Application/Helpers/HttpExtensions.cs create mode 100644 Application/Interfaces/IClaimTypeService.cs create mode 100644 Application/Interfaces/ICustomEventRepository.cs create mode 100644 Application/Interfaces/IDesertStormParticipantRepository.cs create mode 100644 Application/Interfaces/IEmailTemplate.cs create mode 100644 Application/Interfaces/IMarshalGuardParticipantRepository.cs create mode 100644 Application/Interfaces/IRankRepository.cs create mode 100644 Application/Interfaces/IUserRepository.cs create mode 100644 Application/Interfaces/IVsDuelParticipantRepository.cs create mode 100644 Application/Profiles/CustomEventParticipantProfile.cs create mode 100644 Application/Profiles/CustomEventProfile.cs create mode 100644 Application/Profiles/DesertStormParticipantProfile.cs create mode 100644 Application/Profiles/MarshalGuardParticipantProfile.cs create mode 100644 Application/Profiles/RankProfile.cs create mode 100644 Application/Profiles/VsDuelParticipantProfile.cs create mode 100644 Application/Repositories/CustomEventRepository.cs create mode 100644 Application/Repositories/DesertStormParticipantRepository.cs create mode 100644 Application/Repositories/MarshalGuardParticipantRepository.cs create mode 100644 Application/Repositories/RankRepository.cs create mode 100644 Application/Repositories/UserRepository.cs create mode 100644 Application/Repositories/VsDuelParticipantRepository.cs create mode 100644 Application/Services/ClaimTypeService.cs create mode 100644 Database/Configurations/CustomEventConfiguration.cs create mode 100644 Database/Configurations/CustomEventParticipantConfiguration.cs create mode 100644 Database/Configurations/DesertStormParticipantConfiguration.cs create mode 100644 Database/Configurations/MarshalGuardParticipantConfiguration.cs create mode 100644 Database/Configurations/VsDuelParticipantConfiguration.cs create mode 100644 Database/Entities/CustomEvent.cs create mode 100644 Database/Entities/CustomEventParticipant.cs create mode 100644 Database/Entities/DesertStormParticipant.cs create mode 100644 Database/Entities/MarshalGuardParticipant.cs create mode 100644 Database/Entities/VsDuelParticipant.cs delete mode 100644 Database/Migrations/20241001064648_FixAllianceConfiguration.cs rename Database/Migrations/{20241001064648_FixAllianceConfiguration.Designer.cs => 20241113124240_Init.Designer.cs} (60%) rename Database/Migrations/{20240930140736_Init.cs => 20241113124240_Init.cs} (60%) rename Database/Migrations/{20240930140736_Init.Designer.cs => 20241114100107_ChangeIntToLong.Designer.cs} (59%) create mode 100644 Database/Migrations/20241114100107_ChangeIntToLong.cs create mode 100644 Database/Migrations/20241120092041_AddStartPlayerFlagToDesertStormParticipant.Designer.cs create mode 100644 Database/Migrations/20241120092041_AddStartPlayerFlagToDesertStormParticipant.cs create mode 100644 Ui/src/app/Authentication/email-confirmation/email-confirmation.component.css create mode 100644 Ui/src/app/Authentication/email-confirmation/email-confirmation.component.html create mode 100644 Ui/src/app/Authentication/email-confirmation/email-confirmation.component.ts create mode 100644 Ui/src/app/Authentication/forgot-password/forgot-password.component.css create mode 100644 Ui/src/app/Authentication/forgot-password/forgot-password.component.html create mode 100644 Ui/src/app/Authentication/forgot-password/forgot-password.component.ts create mode 100644 Ui/src/app/Authentication/login/login.component.css create mode 100644 Ui/src/app/Authentication/login/login.component.html create mode 100644 Ui/src/app/Authentication/login/login.component.ts create mode 100644 Ui/src/app/Authentication/register/register.component.css create mode 100644 Ui/src/app/Authentication/register/register.component.html create mode 100644 Ui/src/app/Authentication/register/register.component.ts create mode 100644 Ui/src/app/Authentication/reset-password/reset-password.component.css create mode 100644 Ui/src/app/Authentication/reset-password/reset-password.component.html create mode 100644 Ui/src/app/Authentication/reset-password/reset-password.component.ts create mode 100644 Ui/src/app/Authentication/sign-up/sign-up.component.css create mode 100644 Ui/src/app/Authentication/sign-up/sign-up.component.html create mode 100644 Ui/src/app/Authentication/sign-up/sign-up.component.ts create mode 100644 Ui/src/app/guards/auth.guard.ts create mode 100644 Ui/src/app/helpers/constants.ts create mode 100644 Ui/src/app/helpers/days.ts create mode 100644 Ui/src/app/helpers/months.ts create mode 100644 Ui/src/app/helpers/passwordValidators.ts create mode 100644 Ui/src/app/helpers/under-development/under-development.component.css create mode 100644 Ui/src/app/helpers/under-development/under-development.component.html create mode 100644 Ui/src/app/helpers/under-development/under-development.component.ts create mode 100644 Ui/src/app/helpers/week.pipe.ts create mode 100644 Ui/src/app/interceptors/jwt.interceptor.ts create mode 100644 Ui/src/app/interceptors/spinner.interceptor.ts create mode 100644 Ui/src/app/modals/desert-storm-participants-modal/desert-storm-participants-modal.component.css create mode 100644 Ui/src/app/modals/desert-storm-participants-modal/desert-storm-participants-modal.component.html create mode 100644 Ui/src/app/modals/desert-storm-participants-modal/desert-storm-participants-modal.component.ts create mode 100644 Ui/src/app/modals/invite-user-modal/invite-user-modal.component.css create mode 100644 Ui/src/app/modals/invite-user-modal/invite-user-modal.component.html create mode 100644 Ui/src/app/modals/invite-user-modal/invite-user-modal.component.ts create mode 100644 Ui/src/app/modals/marshal-guard-modal/marshal-guard-modal.component.css create mode 100644 Ui/src/app/modals/marshal-guard-modal/marshal-guard-modal.component.html create mode 100644 Ui/src/app/modals/marshal-guard-modal/marshal-guard-modal.component.ts create mode 100644 Ui/src/app/modals/player-admonition-modal/player-admonition-modal.component.css create mode 100644 Ui/src/app/modals/player-admonition-modal/player-admonition-modal.component.html create mode 100644 Ui/src/app/modals/player-admonition-modal/player-admonition-modal.component.ts create mode 100644 Ui/src/app/modals/player-edit-modal/player-edit-modal.component.css create mode 100644 Ui/src/app/modals/player-edit-modal/player-edit-modal.component.html create mode 100644 Ui/src/app/modals/player-edit-modal/player-edit-modal.component.ts create mode 100644 Ui/src/app/modals/player-note-modal/player-note-modal.component.css create mode 100644 Ui/src/app/modals/player-note-modal/player-note-modal.component.html create mode 100644 Ui/src/app/modals/player-note-modal/player-note-modal.component.ts create mode 100644 Ui/src/app/modals/user-edit-modal/user-edit-modal.component.css create mode 100644 Ui/src/app/modals/user-edit-modal/user-edit-modal.component.html create mode 100644 Ui/src/app/modals/user-edit-modal/user-edit-modal.component.ts create mode 100644 Ui/src/app/modals/vs-duel-create-modal/vs-duel-create-modal.component.css create mode 100644 Ui/src/app/modals/vs-duel-create-modal/vs-duel-create-modal.component.html create mode 100644 Ui/src/app/modals/vs-duel-create-modal/vs-duel-create-modal.component.ts create mode 100644 Ui/src/app/models/admonition.model.ts create mode 100644 Ui/src/app/models/alliance.model.ts create mode 100644 Ui/src/app/models/changePassword.model.ts create mode 100644 Ui/src/app/models/confirmEmailRequest.model.ts create mode 100644 Ui/src/app/models/customEvent.model.ts create mode 100644 Ui/src/app/models/customEventParticipant.model.ts create mode 100644 Ui/src/app/models/decodedToken.model.ts create mode 100644 Ui/src/app/models/desertStorm.model.ts create mode 100644 Ui/src/app/models/desertStormParticipant.model.ts create mode 100644 Ui/src/app/models/emailConfirmationRequest.model.ts create mode 100644 Ui/src/app/models/forgotPassword.model.ts create mode 100644 Ui/src/app/models/inviteUser.model.ts create mode 100644 Ui/src/app/models/login.model.ts create mode 100644 Ui/src/app/models/marshalGuard.model.ts create mode 100644 Ui/src/app/models/marshalGuardParticipant.model.ts create mode 100644 Ui/src/app/models/note.model.ts create mode 100644 Ui/src/app/models/player.model.ts create mode 100644 Ui/src/app/models/rank.model.ts create mode 100644 Ui/src/app/models/registerUser.model.ts create mode 100644 Ui/src/app/models/resetPassword.model.ts create mode 100644 Ui/src/app/models/signUp.model.ts create mode 100644 Ui/src/app/models/user.model.ts create mode 100644 Ui/src/app/models/vsDuel.model.ts create mode 100644 Ui/src/app/models/vsDuelParticipant.model.ts create mode 100644 Ui/src/app/navigation/navigation.component.css create mode 100644 Ui/src/app/navigation/navigation.component.html create mode 100644 Ui/src/app/navigation/navigation.component.ts create mode 100644 Ui/src/app/pages/account/account.component.css create mode 100644 Ui/src/app/pages/account/account.component.html create mode 100644 Ui/src/app/pages/account/account.component.ts create mode 100644 Ui/src/app/pages/alliance/alliance.component.css create mode 100644 Ui/src/app/pages/alliance/alliance.component.html create mode 100644 Ui/src/app/pages/alliance/alliance.component.ts create mode 100644 Ui/src/app/pages/change-password/change-password.component.css create mode 100644 Ui/src/app/pages/change-password/change-password.component.html create mode 100644 Ui/src/app/pages/change-password/change-password.component.ts create mode 100644 Ui/src/app/pages/custom-event/custom-event.component.css create mode 100644 Ui/src/app/pages/custom-event/custom-event.component.html create mode 100644 Ui/src/app/pages/custom-event/custom-event.component.ts create mode 100644 Ui/src/app/pages/desert-storm/desert-storm-detail/desert-storm-detail.component.css create mode 100644 Ui/src/app/pages/desert-storm/desert-storm-detail/desert-storm-detail.component.html create mode 100644 Ui/src/app/pages/desert-storm/desert-storm-detail/desert-storm-detail.component.ts create mode 100644 Ui/src/app/pages/desert-storm/desert-storm.component.css create mode 100644 Ui/src/app/pages/desert-storm/desert-storm.component.html create mode 100644 Ui/src/app/pages/desert-storm/desert-storm.component.ts create mode 100644 Ui/src/app/pages/marshal-guard/marshal-guard-detail/marshal-guard-detail.component.css create mode 100644 Ui/src/app/pages/marshal-guard/marshal-guard-detail/marshal-guard-detail.component.html create mode 100644 Ui/src/app/pages/marshal-guard/marshal-guard-detail/marshal-guard-detail.component.ts create mode 100644 Ui/src/app/pages/marshal-guard/marshal-guard.component.css create mode 100644 Ui/src/app/pages/marshal-guard/marshal-guard.component.html create mode 100644 Ui/src/app/pages/marshal-guard/marshal-guard.component.ts create mode 100644 Ui/src/app/pages/player-information/player-info-custom-event/player-info-custom-event.component.css create mode 100644 Ui/src/app/pages/player-information/player-info-custom-event/player-info-custom-event.component.html create mode 100644 Ui/src/app/pages/player-information/player-info-custom-event/player-info-custom-event.component.ts create mode 100644 Ui/src/app/pages/player-information/player-info-desert-storm/player-info-desert-storm.component.css create mode 100644 Ui/src/app/pages/player-information/player-info-desert-storm/player-info-desert-storm.component.html create mode 100644 Ui/src/app/pages/player-information/player-info-desert-storm/player-info-desert-storm.component.ts create mode 100644 Ui/src/app/pages/player-information/player-info-marshal-guard/player-info-marshal-guard.component.css create mode 100644 Ui/src/app/pages/player-information/player-info-marshal-guard/player-info-marshal-guard.component.html create mode 100644 Ui/src/app/pages/player-information/player-info-marshal-guard/player-info-marshal-guard.component.ts create mode 100644 Ui/src/app/pages/player-information/player-info-vs-duel/player-info-vs-duel.component.css create mode 100644 Ui/src/app/pages/player-information/player-info-vs-duel/player-info-vs-duel.component.html create mode 100644 Ui/src/app/pages/player-information/player-info-vs-duel/player-info-vs-duel.component.ts create mode 100644 Ui/src/app/pages/player-information/player-information.component.css create mode 100644 Ui/src/app/pages/player-information/player-information.component.html create mode 100644 Ui/src/app/pages/player-information/player-information.component.ts create mode 100644 Ui/src/app/pages/player/player.component.css create mode 100644 Ui/src/app/pages/player/player.component.html create mode 100644 Ui/src/app/pages/player/player.component.ts create mode 100644 Ui/src/app/pages/vs-duel/vs-duel-detail/vs-duel-detail.component.css create mode 100644 Ui/src/app/pages/vs-duel/vs-duel-detail/vs-duel-detail.component.html create mode 100644 Ui/src/app/pages/vs-duel/vs-duel-detail/vs-duel-detail.component.ts create mode 100644 Ui/src/app/pages/vs-duel/vs-duel-edit/vs-duel-edit.component.css create mode 100644 Ui/src/app/pages/vs-duel/vs-duel-edit/vs-duel-edit.component.html create mode 100644 Ui/src/app/pages/vs-duel/vs-duel-edit/vs-duel-edit.component.ts create mode 100644 Ui/src/app/pages/vs-duel/vs-duel.component.css create mode 100644 Ui/src/app/pages/vs-duel/vs-duel.component.html create mode 100644 Ui/src/app/pages/vs-duel/vs-duel.component.ts create mode 100644 Ui/src/app/services/admonition.service.ts create mode 100644 Ui/src/app/services/alliance.service.ts create mode 100644 Ui/src/app/services/authentication.service.ts create mode 100644 Ui/src/app/services/desert-storm-participant.service.ts create mode 100644 Ui/src/app/services/desert-storm.service.ts create mode 100644 Ui/src/app/services/jwt-token.service.ts create mode 100644 Ui/src/app/services/marshal-guard-participant.service.ts create mode 100644 Ui/src/app/services/marshal-guard.service.ts create mode 100644 Ui/src/app/services/notes.service.ts create mode 100644 Ui/src/app/services/player.service.ts create mode 100644 Ui/src/app/services/rank.service.ts create mode 100644 Ui/src/app/services/spinner.service.ts create mode 100644 Ui/src/app/services/user.service.ts create mode 100644 Ui/src/app/services/vs-duel-participant.service.ts create mode 100644 Ui/src/app/services/vs-duel.service.ts create mode 100644 Ui/src/assets/images/background.jpg create mode 100644 Utilities/Classes/EmailConfiguration.cs create mode 100644 Utilities/Classes/EmailMessage.cs create mode 100644 Utilities/Interfaces/IEmailService.cs create mode 100644 Utilities/Services/EmailService.cs 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 0000000000000000000000000000000000000000..77c642a4bc109e41e81bf195352d5c0b7c55fed6 GIT binary patch literal 44243 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 a?k?v1<0eo5)*6HtH?#y3(lv+TH2>Kr_X~yq literal 0 HcmV?d00001 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 + + + + + + + +