From 711fb4adaa3cecf50ca21d7e2b228b37d0689886 Mon Sep 17 00:00:00 2001 From: Tomasi - Developing Date: Wed, 23 Apr 2025 16:03:41 +0200 Subject: [PATCH] v 0.9.0 --- .../v1/CustomEventCategoriesController.cs | 134 ++ .../v1/CustomEventLeaderboardsController.cs | 83 ++ Application/ApplicationDependencyInjection.cs | 2 + .../CustomEvent/CreateCustomEventDto.cs | 2 + .../CustomEvent/CustomEventDto.cs | 4 + .../CustomEvent/UpdateCustomEventDto.cs | 2 + .../CreateCustomEventCategoryDto.cs | 19 + .../CustomEventCategoryDto.cs | 14 + .../UpdateCustomEventCategoryDto.cs | 19 + .../LeaderboardParticipationEventDto.cs | 8 + ...eaderboardPointAndParticipationEventDto.cs | 12 + .../LeaderboardPointEventDto.cs | 8 + .../Errors/CustomEventCategoryErrors.cs | 9 + .../ICustomEventCategoryRepository.cs | 17 + .../Profiles/CustomEventCategoryProfile.cs | 18 + Application/Profiles/CustomEventProfile.cs | 3 +- .../CustomEventCategoryRepository.cs | 111 ++ .../CustomEventLeaderboardRepository.cs | 155 ++ .../ICustomEventLeaderboardRepository.cs | 14 + Database/ApplicationContext.cs | 2 + .../CustomEventCategoryConfiguration.cs | 24 + .../CustomEventConfiguration.cs | 5 + Database/Entities/Alliance.cs | 2 + Database/Entities/CustomEvent.cs | 4 + Database/Entities/CustomEventCategory.cs | 16 + ...062713_Add_CustomEventCategory.Designer.cs | 1286 +++++++++++++++++ .../20250417062713_Add_CustomEventCategory.cs | 93 ++ ...312_Update_CustomEventCategory.Designer.cs | 1286 +++++++++++++++++ ...250417071312_Update_CustomEventCategory.cs | 128 ++ .../ApplicationContextModelSnapshot.cs | 65 +- README.md | 17 +- Ui/src/app/app.module.ts | 8 +- Ui/src/app/models/customEvent.model.ts | 3 + .../app/models/customEventCategory.model.ts | 21 + .../models/customEventLeaderboard.model.ts | 16 + .../custom-event-category.component.css | 0 .../custom-event-category.component.html | 94 ++ .../custom-event-category.component.ts | 156 ++ .../custom-event-detail.component.html | 2 +- .../custom-event-events.component.css | 0 .../custom-event-events.component.html | 176 +++ .../custom-event-events.component.ts | 312 ++++ .../custom-event-leaderboard.component.css | 0 .../custom-event-leaderboard.component.html | 278 ++++ .../custom-event-leaderboard.component.ts | 130 ++ .../custom-event/custom-event.component.html | 186 +-- .../custom-event/custom-event.component.ts | 268 +--- .../services/custom-event-category.service.ts | 38 + .../custom-event-leaderboard.service.ts | 31 + 49 files changed, 4842 insertions(+), 439 deletions(-) create mode 100644 Api/Controllers/v1/CustomEventCategoriesController.cs create mode 100644 Api/Controllers/v1/CustomEventLeaderboardsController.cs create mode 100644 Application/DataTransferObjects/CustomEventCategory/CreateCustomEventCategoryDto.cs create mode 100644 Application/DataTransferObjects/CustomEventCategory/CustomEventCategoryDto.cs create mode 100644 Application/DataTransferObjects/CustomEventCategory/UpdateCustomEventCategoryDto.cs create mode 100644 Application/DataTransferObjects/CustomEventLeaderboard/LeaderboardParticipationEventDto.cs create mode 100644 Application/DataTransferObjects/CustomEventLeaderboard/LeaderboardPointAndParticipationEventDto.cs create mode 100644 Application/DataTransferObjects/CustomEventLeaderboard/LeaderboardPointEventDto.cs create mode 100644 Application/Errors/CustomEventCategoryErrors.cs create mode 100644 Application/Interfaces/ICustomEventCategoryRepository.cs create mode 100644 Application/Profiles/CustomEventCategoryProfile.cs create mode 100644 Application/Repositories/CustomEventCategoryRepository.cs create mode 100644 Application/Repositories/CustomEventLeaderboardRepository.cs create mode 100644 Application/Repositories/ICustomEventLeaderboardRepository.cs create mode 100644 Database/Configurations/CustomEventCategoryConfiguration.cs create mode 100644 Database/Entities/CustomEventCategory.cs create mode 100644 Database/Migrations/20250417062713_Add_CustomEventCategory.Designer.cs create mode 100644 Database/Migrations/20250417062713_Add_CustomEventCategory.cs create mode 100644 Database/Migrations/20250417071312_Update_CustomEventCategory.Designer.cs create mode 100644 Database/Migrations/20250417071312_Update_CustomEventCategory.cs create mode 100644 Ui/src/app/models/customEventCategory.model.ts create mode 100644 Ui/src/app/models/customEventLeaderboard.model.ts create mode 100644 Ui/src/app/pages/custom-event/custom-event-category/custom-event-category.component.css create mode 100644 Ui/src/app/pages/custom-event/custom-event-category/custom-event-category.component.html create mode 100644 Ui/src/app/pages/custom-event/custom-event-category/custom-event-category.component.ts create mode 100644 Ui/src/app/pages/custom-event/custom-event-events/custom-event-events.component.css create mode 100644 Ui/src/app/pages/custom-event/custom-event-events/custom-event-events.component.html create mode 100644 Ui/src/app/pages/custom-event/custom-event-events/custom-event-events.component.ts create mode 100644 Ui/src/app/pages/custom-event/custom-event-leaderboard/custom-event-leaderboard.component.css create mode 100644 Ui/src/app/pages/custom-event/custom-event-leaderboard/custom-event-leaderboard.component.html create mode 100644 Ui/src/app/pages/custom-event/custom-event-leaderboard/custom-event-leaderboard.component.ts create mode 100644 Ui/src/app/services/custom-event-category.service.ts create mode 100644 Ui/src/app/services/custom-event-leaderboard.service.ts diff --git a/Api/Controllers/v1/CustomEventCategoriesController.cs b/Api/Controllers/v1/CustomEventCategoriesController.cs new file mode 100644 index 0000000..d7da754 --- /dev/null +++ b/Api/Controllers/v1/CustomEventCategoriesController.cs @@ -0,0 +1,134 @@ +using Application.DataTransferObjects.CustomEventCategory; +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 CustomEventCategoriesController(ICustomEventCategoryRepository customEventCategoryRepository, ILogger logger) : ControllerBase + { + [HttpGet("{customEventCategoryId:guid}")] + public async Task> GetCustomEventCategory(Guid customEventCategoryId, CancellationToken cancellationToken) + { + try + { + var customEventCategoryResult = + await customEventCategoryRepository.GetCustomEventCategoryAsync(customEventCategoryId, cancellationToken); + + return customEventCategoryResult.IsFailure + ? BadRequest(customEventCategoryResult.Error) + : Ok(customEventCategoryResult.Value); + } + catch (Exception e) + { + logger.LogError(e, "{ErrorMessage}", e.Message); + return Problem( + detail: $"Failed to process {nameof(GetCustomEventCategory)}", + statusCode: StatusCodes.Status500InternalServerError, + title: "Internal server error"); + } + } + + [HttpGet("Alliance/{allianceId:guid}")] + public async Task>> GetAllianceCustomEventCategories(Guid allianceId, CancellationToken cancellationToken) + { + try + { + var allianceCustomEventCategoriesResult = + await customEventCategoryRepository.GetAllianceCustomEventCategoriesAsync(allianceId, cancellationToken); + + if (allianceCustomEventCategoriesResult.IsFailure) return BadRequest(allianceCustomEventCategoriesResult.Error); + + return allianceCustomEventCategoriesResult.Value.Count > 0 + ? Ok(allianceCustomEventCategoriesResult.Value) + : NoContent(); + } + catch (Exception e) + { + logger.LogError(e, "{ErrorMessage}", e.Message); + return Problem( + detail: $"Failed to process {nameof(GetAllianceCustomEventCategories)}", + statusCode: StatusCodes.Status500InternalServerError, + title: "Internal server error"); + } + } + + [HttpPost] + public async Task> CreateCustomEventCategory(CreateCustomEventCategoryDto createCustomEventCategoryDto, + CancellationToken cancellationToken) + { + try + { + if (!ModelState.IsValid) return UnprocessableEntity(ModelState); + + var createResult = await customEventCategoryRepository.CreateCustomEventCategoryAsync(createCustomEventCategoryDto, cancellationToken); + + return createResult.IsFailure + ? BadRequest(createResult.Error) + : CreatedAtAction(nameof(GetCustomEventCategory), new { customEventCategoryId = createResult.Value.Id }, + createResult.Value); + } + catch (Exception e) + { + logger.LogError(e, "{ErrorMessage}", e.Message); + return Problem( + detail: $"Failed to process {nameof(CreateCustomEventCategory)}", + statusCode: StatusCodes.Status500InternalServerError, + title: "Internal server error"); + } + } + + [HttpPut("{customEventCategoryId:guid}")] + public async Task> UpdateCustomEventCategory(Guid customEventCategoryId, + UpdateCustomEventCategoryDto updateCustomEventCategoryDto, CancellationToken cancellationToken) + { + try + { + if (!ModelState.IsValid) return UnprocessableEntity(ModelState); + + if (updateCustomEventCategoryDto.Id != customEventCategoryId) return Conflict(CustomEventCategoryErrors.IdConflict); + + var updateResult = await customEventCategoryRepository.UpdateCustomEventCategoryAsync(updateCustomEventCategoryDto, cancellationToken); + + return updateResult.IsFailure + ? BadRequest(updateResult.Error) + : Ok(updateResult.Value); + } + catch (Exception e) + { + logger.LogError(e, "{ErrorMessage}", e.Message); + return Problem( + detail: $"Failed to process {nameof(UpdateCustomEventCategory)}", + statusCode: StatusCodes.Status500InternalServerError, + title: "Internal server error"); + } + } + + [HttpDelete("{customEventCategoryId:guid}")] + public async Task> DeleteCustomEventCategory(Guid customEventCategoryId, CancellationToken cancellationToken) + { + try + { + var deleteResult = await customEventCategoryRepository.DeleteCustomEventAsync(customEventCategoryId, cancellationToken); + + return deleteResult.IsFailure + ? BadRequest(deleteResult.Error) + : Ok(deleteResult.Value); + } + catch (Exception e) + { + logger.LogError(e, "{ErrorMessage}", e.Message); + return Problem( + detail: $"Failed to process {nameof(DeleteCustomEventCategory)}", + statusCode: StatusCodes.Status500InternalServerError, + title: "Internal server error"); + } + } + } +} diff --git a/Api/Controllers/v1/CustomEventLeaderboardsController.cs b/Api/Controllers/v1/CustomEventLeaderboardsController.cs new file mode 100644 index 0000000..6f5682b --- /dev/null +++ b/Api/Controllers/v1/CustomEventLeaderboardsController.cs @@ -0,0 +1,83 @@ +using Application.DataTransferObjects.CustomEventLeaderboard; +using Application.Repositories; +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 CustomEventLeaderboardsController(ICustomEventLeaderBoardRepository customEventLeaderBoardRepository, ILogger logger) : ControllerBase + { + [HttpGet("point/{customEventCategoryId:guid}")] + public async Task> GetPointEvent(Guid customEventCategoryId, + CancellationToken cancellationToken) + { + try + { + var pointEventLeaderboardResult = await customEventLeaderBoardRepository.GetPointEventLeaderboardAsync(customEventCategoryId, cancellationToken); + + if (pointEventLeaderboardResult.IsFailure) return BadRequest(pointEventLeaderboardResult.Error); + + return pointEventLeaderboardResult.Value.Count > 0 + ? Ok(pointEventLeaderboardResult.Value) + : NoContent(); + } + catch (Exception e) + { + logger.LogError(e, "{ErrorMessage}", e.Message); + return Problem( + detail: $"Failed to process {nameof(GetPointEvent)}", + statusCode: StatusCodes.Status500InternalServerError, + title: "Internal server error"); + } + } + + [HttpGet("participation/{customEventCategoryId:guid}")] + public async Task> GetParticipationEvent(Guid customEventCategoryId, + CancellationToken cancellationToken) + { + try + { + var participationEventLeaderboardResult = await customEventLeaderBoardRepository.GetParticipationEventLeaderboardAsync(customEventCategoryId, cancellationToken); + if (participationEventLeaderboardResult.IsFailure) return BadRequest(participationEventLeaderboardResult.Error); + return participationEventLeaderboardResult.Value.Count > 0 + ? Ok(participationEventLeaderboardResult.Value) + : NoContent(); + } + catch (Exception e) + { + logger.LogError(e, "{ErrorMessage}", e.Message); + return Problem( + detail: $"Failed to process {nameof(GetParticipationEvent)}", + statusCode: StatusCodes.Status500InternalServerError, + title: "Internal server error"); + } + } + + [HttpGet("point-and-participation/{customEventCategoryId:guid}")] + public async Task> GetPointAndParticipationEvent(Guid customEventCategoryId, + CancellationToken cancellationToken) + { + try + { + var pointAndParticipationEventLeaderboardResult = await customEventLeaderBoardRepository.GetPointAndParticipationEventLeaderboardAsync(customEventCategoryId, cancellationToken); + if (pointAndParticipationEventLeaderboardResult.IsFailure) return BadRequest(pointAndParticipationEventLeaderboardResult.Error); + return pointAndParticipationEventLeaderboardResult.Value.Count > 0 + ? Ok(pointAndParticipationEventLeaderboardResult.Value) + : NoContent(); + } + catch (Exception e) + { + logger.LogError(e, "{ErrorMessage}", e.Message); + return Problem( + detail: $"Failed to process {nameof(GetPointAndParticipationEvent)}", + statusCode: StatusCodes.Status500InternalServerError, + title: "Internal server error"); + } + } + } +} diff --git a/Application/ApplicationDependencyInjection.cs b/Application/ApplicationDependencyInjection.cs index f146898..18f9510 100644 --- a/Application/ApplicationDependencyInjection.cs +++ b/Application/ApplicationDependencyInjection.cs @@ -34,6 +34,8 @@ public static class ApplicationDependencyInjection services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddTransient(); diff --git a/Application/DataTransferObjects/CustomEvent/CreateCustomEventDto.cs b/Application/DataTransferObjects/CustomEvent/CreateCustomEventDto.cs index 09c25c0..284f3c9 100644 --- a/Application/DataTransferObjects/CustomEvent/CreateCustomEventDto.cs +++ b/Application/DataTransferObjects/CustomEvent/CreateCustomEventDto.cs @@ -21,6 +21,8 @@ public class CreateCustomEventDto [Required] public Guid AllianceId { get; set; } + public Guid? CustomEventCategoryId { get; set; } + [Required] public required string EventDate { get; set; } diff --git a/Application/DataTransferObjects/CustomEvent/CustomEventDto.cs b/Application/DataTransferObjects/CustomEvent/CustomEventDto.cs index bd2dd71..3a6e462 100644 --- a/Application/DataTransferObjects/CustomEvent/CustomEventDto.cs +++ b/Application/DataTransferObjects/CustomEvent/CustomEventDto.cs @@ -6,6 +6,10 @@ public class CustomEventDto public Guid AllianceId { get; set; } + public Guid? CustomEventCategoryId { get; set; } + + public string? CategoryName { get; set; } + public required string Name { get; set; } public required string Description { get; set; } diff --git a/Application/DataTransferObjects/CustomEvent/UpdateCustomEventDto.cs b/Application/DataTransferObjects/CustomEvent/UpdateCustomEventDto.cs index 183d17d..e3b0e9e 100644 --- a/Application/DataTransferObjects/CustomEvent/UpdateCustomEventDto.cs +++ b/Application/DataTransferObjects/CustomEvent/UpdateCustomEventDto.cs @@ -7,6 +7,8 @@ public class UpdateCustomEventDto [Required] public Guid Id { get; set; } + public Guid? CustomEventCategoryId { get; set; } + [Required] [MaxLength(150)] public required string Name { get; set; } diff --git a/Application/DataTransferObjects/CustomEventCategory/CreateCustomEventCategoryDto.cs b/Application/DataTransferObjects/CustomEventCategory/CreateCustomEventCategoryDto.cs new file mode 100644 index 0000000..062dc66 --- /dev/null +++ b/Application/DataTransferObjects/CustomEventCategory/CreateCustomEventCategoryDto.cs @@ -0,0 +1,19 @@ +using System.ComponentModel.DataAnnotations; + +namespace Application.DataTransferObjects.CustomEventCategory; + +public class CreateCustomEventCategoryDto +{ + [Required] + public Guid AllianceId { get; set; } + + [Required] + [MaxLength(250)] + public required string Name { get; set; } + + [Required] + public bool IsPointsEvent { get; set; } + + [Required] + public bool IsParticipationEvent { get; set; } +} \ No newline at end of file diff --git a/Application/DataTransferObjects/CustomEventCategory/CustomEventCategoryDto.cs b/Application/DataTransferObjects/CustomEventCategory/CustomEventCategoryDto.cs new file mode 100644 index 0000000..699a132 --- /dev/null +++ b/Application/DataTransferObjects/CustomEventCategory/CustomEventCategoryDto.cs @@ -0,0 +1,14 @@ +namespace Application.DataTransferObjects.CustomEventCategory; + +public class CustomEventCategoryDto +{ + public Guid Id { get; set; } + + public Guid AllianceId { get; set; } + + public required string Name { get; set; } + + public bool IsPointsEvent { get; set; } + + public bool IsParticipationEvent { get; set; } +} \ No newline at end of file diff --git a/Application/DataTransferObjects/CustomEventCategory/UpdateCustomEventCategoryDto.cs b/Application/DataTransferObjects/CustomEventCategory/UpdateCustomEventCategoryDto.cs new file mode 100644 index 0000000..47cc1b1 --- /dev/null +++ b/Application/DataTransferObjects/CustomEventCategory/UpdateCustomEventCategoryDto.cs @@ -0,0 +1,19 @@ +using System.ComponentModel.DataAnnotations; + +namespace Application.DataTransferObjects.CustomEventCategory; + +public class UpdateCustomEventCategoryDto +{ + [Required] + public Guid Id { get; set; } + + [Required] + [MaxLength(250)] + public required string Name { get; set; } + + [Required] + public bool IsPointsEvent { get; set; } + + [Required] + public bool IsParticipationEvent { get; set; } +} \ No newline at end of file diff --git a/Application/DataTransferObjects/CustomEventLeaderboard/LeaderboardParticipationEventDto.cs b/Application/DataTransferObjects/CustomEventLeaderboard/LeaderboardParticipationEventDto.cs new file mode 100644 index 0000000..9ccb341 --- /dev/null +++ b/Application/DataTransferObjects/CustomEventLeaderboard/LeaderboardParticipationEventDto.cs @@ -0,0 +1,8 @@ +namespace Application.DataTransferObjects.CustomEventLeaderboard; + +public class LeaderboardParticipationEventDto +{ + public required string PlayerName { get; set; } + + public int Participations { get; set; } +} \ No newline at end of file diff --git a/Application/DataTransferObjects/CustomEventLeaderboard/LeaderboardPointAndParticipationEventDto.cs b/Application/DataTransferObjects/CustomEventLeaderboard/LeaderboardPointAndParticipationEventDto.cs new file mode 100644 index 0000000..2387f6b --- /dev/null +++ b/Application/DataTransferObjects/CustomEventLeaderboard/LeaderboardPointAndParticipationEventDto.cs @@ -0,0 +1,12 @@ +namespace Application.DataTransferObjects.CustomEventLeaderboard; + +public class LeaderboardPointAndParticipationEventDto +{ + public required string PlayerName { get; set; } + + public long Points { get; set; } + + public int Participations { get; set; } + + public double TotalPoints { get; set; } +} \ No newline at end of file diff --git a/Application/DataTransferObjects/CustomEventLeaderboard/LeaderboardPointEventDto.cs b/Application/DataTransferObjects/CustomEventLeaderboard/LeaderboardPointEventDto.cs new file mode 100644 index 0000000..f2914d3 --- /dev/null +++ b/Application/DataTransferObjects/CustomEventLeaderboard/LeaderboardPointEventDto.cs @@ -0,0 +1,8 @@ +namespace Application.DataTransferObjects.CustomEventLeaderboard; + +public class LeaderboardPointEventDto +{ + public required string PlayerName { get; set; } + + public long Points { get; set; } +} \ No newline at end of file diff --git a/Application/Errors/CustomEventCategoryErrors.cs b/Application/Errors/CustomEventCategoryErrors.cs new file mode 100644 index 0000000..3f04685 --- /dev/null +++ b/Application/Errors/CustomEventCategoryErrors.cs @@ -0,0 +1,9 @@ +namespace Application.Errors; + +public class CustomEventCategoryErrors +{ + public static readonly Error NotFound = new("Error.CustomEventCategory.NotFound", + "The custom event category with the specified identifier was not found"); + + public static readonly Error IdConflict = new("Error.CustomEventCategory.IdConflict", "There is a conflict with the id's"); +} \ No newline at end of file diff --git a/Application/Interfaces/ICustomEventCategoryRepository.cs b/Application/Interfaces/ICustomEventCategoryRepository.cs new file mode 100644 index 0000000..a56dd4f --- /dev/null +++ b/Application/Interfaces/ICustomEventCategoryRepository.cs @@ -0,0 +1,17 @@ +using Application.Classes; +using Application.DataTransferObjects.CustomEventCategory; + +namespace Application.Interfaces; + +public interface ICustomEventCategoryRepository +{ + Task> GetCustomEventCategoryAsync(Guid customEventCategoryId, CancellationToken cancellationToken); + + Task>> GetAllianceCustomEventCategoriesAsync(Guid allianceId, CancellationToken cancellationToken); + + Task> CreateCustomEventCategoryAsync(CreateCustomEventCategoryDto createCustomEventCategoryDto, CancellationToken cancellationToken); + + Task> UpdateCustomEventCategoryAsync(UpdateCustomEventCategoryDto updateCustomEventCategoryDto, CancellationToken cancellationToken); + + Task> DeleteCustomEventAsync(Guid customEventId, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/Application/Profiles/CustomEventCategoryProfile.cs b/Application/Profiles/CustomEventCategoryProfile.cs new file mode 100644 index 0000000..817bd0e --- /dev/null +++ b/Application/Profiles/CustomEventCategoryProfile.cs @@ -0,0 +1,18 @@ +using Application.DataTransferObjects.CustomEventCategory; +using AutoMapper; +using Database.Entities; + +namespace Application.Profiles; + +public class CustomEventCategoryProfile : Profile +{ + public CustomEventCategoryProfile() + { + CreateMap(); + + CreateMap() + .ForMember(des => des.Id, opt => opt.MapFrom(src => Guid.CreateVersion7())); + + CreateMap(); + } +} \ No newline at end of file diff --git a/Application/Profiles/CustomEventProfile.cs b/Application/Profiles/CustomEventProfile.cs index 68bdb1c..029e099 100644 --- a/Application/Profiles/CustomEventProfile.cs +++ b/Application/Profiles/CustomEventProfile.cs @@ -8,7 +8,8 @@ public class CustomEventProfile : Profile { public CustomEventProfile() { - CreateMap(); + CreateMap() + .ForMember(des => des.CategoryName, opt => opt.MapFrom(src => src.CustomEventCategory!.Name)); CreateMap() .ForMember(des => des.CustomEventParticipants, opt => opt.MapFrom(src => src.CustomEventParticipants)); diff --git a/Application/Repositories/CustomEventCategoryRepository.cs b/Application/Repositories/CustomEventCategoryRepository.cs new file mode 100644 index 0000000..126291a --- /dev/null +++ b/Application/Repositories/CustomEventCategoryRepository.cs @@ -0,0 +1,111 @@ +using Application.Classes; +using Application.DataTransferObjects.CustomEventCategory; +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 CustomEventCategoryRepository(ApplicationContext dbContext, IMapper mapper, ILogger logger) : ICustomEventCategoryRepository +{ + public async Task> GetCustomEventCategoryAsync(Guid customEventCategoryId, CancellationToken cancellationToken) + { + var customEventCategoryById = await dbContext.CustomEventCategories + .ProjectTo(mapper.ConfigurationProvider) + .AsNoTracking() + .FirstOrDefaultAsync(customEventCategory => customEventCategory.Id == customEventCategoryId, cancellationToken); + + return customEventCategoryById is null + ? Result.Failure(CustomEventCategoryErrors.NotFound) + : Result.Success(customEventCategoryById); + } + + public async Task>> GetAllianceCustomEventCategoriesAsync(Guid allianceId, CancellationToken cancellationToken) + { + + var customEventCategories = await dbContext.CustomEventCategories + .Where(customEventCategory => customEventCategory.AllianceId == allianceId) + .ProjectTo(mapper.ConfigurationProvider) + .OrderByDescending(customEventCategory => customEventCategory.Id) + .AsNoTracking() + .ToListAsync(cancellationToken); + + return Result.Success(customEventCategories); + } + + public async Task> CreateCustomEventCategoryAsync(CreateCustomEventCategoryDto createCustomEventCategoryDto, + CancellationToken cancellationToken) + { + try + { + var newCustomEventCategory = mapper.Map(createCustomEventCategoryDto); + await dbContext.CustomEventCategories.AddAsync(newCustomEventCategory, cancellationToken); + await dbContext.SaveChangesAsync(cancellationToken); + var customEventCategoryDto = mapper.Map(newCustomEventCategory); + return Result.Success(customEventCategoryDto); + } + catch (Exception e) + { + logger.LogError(e, "{ErrorMessage}", e.Message); + return Result.Failure(GeneralErrors.DatabaseError); + } + } + + public async Task> UpdateCustomEventCategoryAsync(UpdateCustomEventCategoryDto updateCustomEventCategoryDto, + CancellationToken cancellationToken) + { + var customEventCategoryToUpdate = await dbContext.CustomEventCategories + .FirstOrDefaultAsync(customEventCategory => customEventCategory.Id == updateCustomEventCategoryDto.Id, cancellationToken); + if (customEventCategoryToUpdate is null) + { + return Result.Failure(CustomEventCategoryErrors.NotFound); + } + + try + { + mapper.Map(updateCustomEventCategoryDto, customEventCategoryToUpdate); + await dbContext.SaveChangesAsync(cancellationToken); + var customEventCategoryDto = mapper.Map(customEventCategoryToUpdate); + return Result.Success(customEventCategoryDto); + } + catch (Exception e) + { + logger.LogError(e, "{ErrorMessage}", e.Message); + return Result.Failure(GeneralErrors.DatabaseError); + } + } + + public async Task> DeleteCustomEventAsync(Guid customEventId, CancellationToken cancellationToken) + { + var customEventToDelete = await dbContext.CustomEventCategories + .FirstOrDefaultAsync(customEvent => customEvent.Id == customEventId, cancellationToken); + if (customEventToDelete is null) + { + return Result.Failure(CustomEventCategoryErrors.NotFound); + } + + try + { + var customEvents = await dbContext.CustomEvents + .Where(customEvent => customEvent.CustomEventCategoryId == customEventToDelete.Id) + .ToListAsync(cancellationToken); + if (customEvents.Any()) + { + dbContext.CustomEvents.RemoveRange(customEvents); + } + dbContext.CustomEventCategories.Remove(customEventToDelete); + await dbContext.SaveChangesAsync(cancellationToken); + return Result.Success(true); + } + catch (Exception e) + { + logger.LogError(e, "{ErrorMessage}", e.Message); + return Result.Failure(GeneralErrors.DatabaseError); + } + } +} \ No newline at end of file diff --git a/Application/Repositories/CustomEventLeaderboardRepository.cs b/Application/Repositories/CustomEventLeaderboardRepository.cs new file mode 100644 index 0000000..d2fb12e --- /dev/null +++ b/Application/Repositories/CustomEventLeaderboardRepository.cs @@ -0,0 +1,155 @@ +using Application.Classes; +using Application.DataTransferObjects.CustomEventLeaderboard; +using Database; +using Microsoft.EntityFrameworkCore; + +namespace Application.Repositories; + +public class CustomEventLeaderboardRepository(ApplicationContext dbContext) : ICustomEventLeaderBoardRepository +{ + public async Task>> GetPointEventLeaderboardAsync(Guid customEventCategoryId, CancellationToken cancellationToken) + { + var category = await dbContext.CustomEventCategories + .Where(category => category.Id == customEventCategoryId) + .Include(c => c.CustomEvents) + .ThenInclude(e => e.CustomEventParticipants) + .ThenInclude(p => p.Player) + .AsNoTracking() + .FirstOrDefaultAsync(cancellationToken); + + if (category == null) + { + return Result.Success(new List()); + } + + var leaderboard = category.CustomEvents + .SelectMany(e => e.CustomEventParticipants) + .GroupBy(p => new { p.Player.Id, p.Player.PlayerName }) + .Select(g => new LeaderboardPointEventDto + { + PlayerName = g.Key.PlayerName, + Points = g.Sum(p => p.AchievedPoints ?? 0) + }) + .OrderByDescending(l => l.Points) + .ToList(); + + return Result.Success(leaderboard); + } + + public async Task>> GetParticipationEventLeaderboardAsync(Guid customEventCategoryId, CancellationToken cancellationToken) + { + var category = await dbContext.CustomEventCategories + .Where(category => category.Id == customEventCategoryId) + .Include(c => c.CustomEvents) + .ThenInclude(e => e.CustomEventParticipants) + .ThenInclude(p => p.Player) + .AsNoTracking() + .FirstOrDefaultAsync(cancellationToken); + + if (category == null) + { + return Result.Success(new List()); + } + + var leaderboard = category.CustomEvents + .SelectMany(e => e.CustomEventParticipants) + .GroupBy(p => new { p.Player.Id, p.Player.PlayerName }) + .Select(g => new LeaderboardParticipationEventDto() + { + PlayerName = g.Key.PlayerName, + Participations = g.Count(z => z.Participated!.Value) + }) + .OrderByDescending(l => l.Participations) + .ToList(); + + return Result.Success(leaderboard); + } + + public async Task>> GetPointAndParticipationEventLeaderboardAsync(Guid customEventCategoryId, CancellationToken cancellationToken) + { + var category = await dbContext.CustomEventCategories + .Where(category => category.Id == customEventCategoryId) + .Include(c => c.CustomEvents) + .ThenInclude(e => e.CustomEventParticipants) + .ThenInclude(p => p.Player) + .AsNoTracking() + .FirstOrDefaultAsync(cancellationToken); + + if (category == null) + return Result.Success(new List()); + + // Punktetabelle: Platz 1 = 100, Platz 2 = 99, ..., Platz 100 = 1 + var pointTable = Enumerable.Range(1, 100).Select(i => 101 - i).ToArray(); + + // Gewichtungen + const double baseParticipationValue = 10.0; + const double placeWeight = 2.0; + const double scoreWeight = 1.0; + + // Dictionaries zur Akkumulation + var playerPoints = new Dictionary(); + var playerParticipationCount = new Dictionary(); + var playerTotalPoints = new Dictionary(); + var playerNames = new Dictionary(); + + foreach (var ev in category.CustomEvents) + { + // Vorab Platzierung berechnen + var ranked = ev.CustomEventParticipants + .Where(p => p.Participated == true) + .OrderByDescending(p => p.AchievedPoints ?? 0) + .ToList(); + + foreach (var participant in ev.CustomEventParticipants) + { + if (participant.Player == null) continue; + + var playerId = participant.Player.Id; + var playerName = participant.Player.PlayerName; + + // Spielername merken – egal ob teilgenommen oder nicht + playerNames[playerId] = playerName; + + // Nur wenn teilgenommen + if (participant.Participated == true) + { + var score = participant.AchievedPoints ?? 0; + + // Index in Platzierungsliste finden + var place = ranked.FindIndex(p => p.Player?.Id == playerId); + var placePoints = (place >= 0 && place < pointTable.Length) ? pointTable[place] : 1; + var normalizedPlace = placePoints / 100.0; + var scoreBonus = Math.Log10(score + 1); + + var total = baseParticipationValue + (normalizedPlace * placeWeight) + (scoreBonus * scoreWeight); + + playerTotalPoints[playerId] = playerTotalPoints.GetValueOrDefault(playerId) + total; + playerParticipationCount[playerId] = playerParticipationCount.GetValueOrDefault(playerId) + 1; + playerPoints[playerId] = playerPoints.GetValueOrDefault(playerId) + score; + } + } + } + + // Leaderboard für alle Spieler, die in Events vorkommen + var leaderboard = playerNames.Select(entry => + { + var playerId = entry.Key; + var playerName = entry.Value; + + return new LeaderboardPointAndParticipationEventDto + { + PlayerName = playerName, + Points = playerPoints.GetValueOrDefault(playerId), + Participations = playerParticipationCount.GetValueOrDefault(playerId), + TotalPoints = Math.Round(playerTotalPoints.GetValueOrDefault(playerId), 2) + }; + }) + .OrderByDescending(x => x.TotalPoints) + .ThenByDescending(x => x.Participations) + .ToList(); + + return Result.Success(leaderboard); + } + + +} \ No newline at end of file diff --git a/Application/Repositories/ICustomEventLeaderboardRepository.cs b/Application/Repositories/ICustomEventLeaderboardRepository.cs new file mode 100644 index 0000000..3cd0898 --- /dev/null +++ b/Application/Repositories/ICustomEventLeaderboardRepository.cs @@ -0,0 +1,14 @@ +using Application.Classes; +using Application.DataTransferObjects.CustomEventLeaderboard; + + +namespace Application.Repositories; + +public interface ICustomEventLeaderBoardRepository +{ + Task>> GetPointEventLeaderboardAsync(Guid customEventCategoryId, CancellationToken cancellationToken); + + Task>> GetParticipationEventLeaderboardAsync(Guid customEventCategoryId, CancellationToken cancellationToken); + + Task>> GetPointAndParticipationEventLeaderboardAsync(Guid customEventCategoryId, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/Database/ApplicationContext.cs b/Database/ApplicationContext.cs index cccabed..4aacbf7 100644 --- a/Database/ApplicationContext.cs +++ b/Database/ApplicationContext.cs @@ -44,6 +44,8 @@ public class ApplicationContext(DbContextOptions options) : public DbSet ApiKeys { get; set; } + public DbSet CustomEventCategories { get; set; } + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); diff --git a/Database/Configurations/CustomEventCategoryConfiguration.cs b/Database/Configurations/CustomEventCategoryConfiguration.cs new file mode 100644 index 0000000..74d1ca6 --- /dev/null +++ b/Database/Configurations/CustomEventCategoryConfiguration.cs @@ -0,0 +1,24 @@ +using Database.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Database.Configurations; + +public class CustomEventCategoryConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(customEventCategory => customEventCategory.Id); + builder.Property(customEventCategory => customEventCategory.Id).ValueGeneratedNever(); + + builder.Property(customEventCategory => customEventCategory.AllianceId).IsRequired(); + builder.Property(customEventCategory => customEventCategory.Name).IsRequired().HasMaxLength(255); + builder.Property(customEventCategory => customEventCategory.IsPointsEvent).IsRequired(); + builder.Property(customEventCategory => customEventCategory.IsParticipationEvent).IsRequired(); + + builder.HasOne(customEventCategory => customEventCategory.Alliance) + .WithMany(alliance => alliance.CustomEventCategories) + .HasForeignKey(customEventCategory => customEventCategory.AllianceId) + .OnDelete(DeleteBehavior.Cascade); + } +} \ No newline at end of file diff --git a/Database/Configurations/CustomEventConfiguration.cs b/Database/Configurations/CustomEventConfiguration.cs index 2766c43..8546be7 100644 --- a/Database/Configurations/CustomEventConfiguration.cs +++ b/Database/Configurations/CustomEventConfiguration.cs @@ -25,5 +25,10 @@ public class CustomEventConfiguration : IEntityTypeConfiguration .WithMany(a => a.CustomEvents) .HasForeignKey(c => c.AllianceId) .OnDelete(DeleteBehavior.Cascade); + + builder.HasOne(customEvent => customEvent.CustomEventCategory) + .WithMany(customEventCategory => customEventCategory.CustomEvents) + .HasForeignKey(customEvent => customEvent.CustomEventCategoryId) + .OnDelete(DeleteBehavior.Restrict); } } \ No newline at end of file diff --git a/Database/Entities/Alliance.cs b/Database/Entities/Alliance.cs index b0702b6..41ce0d5 100644 --- a/Database/Entities/Alliance.cs +++ b/Database/Entities/Alliance.cs @@ -29,4 +29,6 @@ public class Alliance : BaseEntity public ICollection VsDuels { get; set; } = []; public ICollection ZombieSieges { get; set; } = []; + + public ICollection CustomEventCategories { get; set; } = []; } \ No newline at end of file diff --git a/Database/Entities/CustomEvent.cs b/Database/Entities/CustomEvent.cs index 4d3e9b6..b2ca737 100644 --- a/Database/Entities/CustomEvent.cs +++ b/Database/Entities/CustomEvent.cs @@ -6,6 +6,10 @@ public class CustomEvent : BaseEntity public Alliance Alliance { get; set; } = null!; + public Guid? CustomEventCategoryId { get; set; } + + public CustomEventCategory? CustomEventCategory { get; set; } + public required string Name { get; set; } public required string Description { get; set; } diff --git a/Database/Entities/CustomEventCategory.cs b/Database/Entities/CustomEventCategory.cs new file mode 100644 index 0000000..5e9a2d6 --- /dev/null +++ b/Database/Entities/CustomEventCategory.cs @@ -0,0 +1,16 @@ +namespace Database.Entities; + +public class CustomEventCategory : BaseEntity +{ + public Guid AllianceId { get; set; } + + public Alliance Alliance { get; set; } = null!; + + public required string Name { get; set; } + + public bool IsPointsEvent { get; set; } + + public bool IsParticipationEvent { get; set; } + + public ICollection CustomEvents { get; set; } = []; +} \ No newline at end of file diff --git a/Database/Migrations/20250417062713_Add_CustomEventCategory.Designer.cs b/Database/Migrations/20250417062713_Add_CustomEventCategory.Designer.cs new file mode 100644 index 0000000..ddbbb9f --- /dev/null +++ b/Database/Migrations/20250417062713_Add_CustomEventCategory.Designer.cs @@ -0,0 +1,1286 @@ +// +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("20250417062713_Add_CustomEventCategory")] + partial class Add_CustomEventCategory + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("dbo") + .HasAnnotation("ProductVersion", "9.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Database.Entities.Admonition", b => + { + b.Property("Id") + .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") + .HasColumnType("uniqueidentifier"); + + b.Property("Abbreviation") + .IsRequired() + .HasMaxLength(5) + .HasColumnType("nvarchar(5)"); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + 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.ApiKey", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("AllianceId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("EncryptedKey") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("ModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("AllianceId") + .IsUnique(); + + b.ToTable("ApiKeys", "dbo"); + }); + + modelBuilder.Entity("Database.Entities.CustomEvent", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("AllianceId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("CustomEventCategoryId") + .HasColumnType("uniqueidentifier"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("EventDate") + .HasColumnType("datetime2"); + + b.Property("IsInProgress") + .HasColumnType("bit"); + + 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.HasIndex("CustomEventCategoryId"); + + b.ToTable("CustomEvents", "dbo"); + }); + + modelBuilder.Entity("Database.Entities.CustomEventCategory", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("AllianceId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsParticipationEvent") + .HasColumnType("bit"); + + b.Property("IsPointsEvent") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("AllianceId"); + + b.ToTable("CustomEventCategory", "dbo"); + }); + + modelBuilder.Entity("Database.Entities.CustomEventParticipant", b => + { + b.Property("Id") + .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") + .HasColumnType("uniqueidentifier"); + + b.Property("AllianceId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("EventDate") + .HasColumnType("datetime2"); + + b.Property("IsInProgress") + .HasColumnType("bit"); + + 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") + .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") + .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") + .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") + .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("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") + .HasColumnType("uniqueidentifier"); + + b.Property("AllianceId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("DismissalReason") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("DismissedAt") + .HasColumnType("datetime2"); + + b.Property("IsDismissed") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + 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") + .HasColumnType("uniqueidentifier"); + + b.Property("AllianceId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("EventDate") + .HasColumnType("datetime2"); + + b.Property("IsInProgress") + .HasColumnType("bit"); + + 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("VsDuelLeagueId") + .HasColumnType("uniqueidentifier"); + + b.Property("Won") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AllianceId"); + + b.HasIndex("VsDuelLeagueId"); + + b.ToTable("VsDuels", "dbo"); + }); + + modelBuilder.Entity("Database.Entities.VsDuelLeague", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Code") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.HasKey("Id"); + + b.ToTable("VsDuelLeagues", "dbo"); + + b.HasData( + new + { + Id = new Guid("0196426e-146a-70d7-b27a-8b369fb55ace"), + Code = 1, + Name = "Silver League" + }, + new + { + Id = new Guid("0196426e-146a-7741-9d31-c5fb1224e163"), + Code = 2, + Name = "Gold League" + }, + new + { + Id = new Guid("0196426e-146a-759d-970c-668552198c41"), + Code = 3, + Name = "Diamond League" + }); + }); + + modelBuilder.Entity("Database.Entities.VsDuelParticipant", b => + { + b.Property("Id") + .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("Database.Entities.ZombieSiege", b => + { + b.Property("Id") + .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.HasKey("Id"); + + b.HasIndex("AllianceId"); + + b.ToTable("ZombieSieges", "dbo"); + }); + + modelBuilder.Entity("Database.Entities.ZombieSiegeParticipant", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("PlayerId") + .HasColumnType("uniqueidentifier"); + + b.Property("SurvivedWaves") + .HasColumnType("int"); + + b.Property("ZombieSiegeId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.HasIndex("ZombieSiegeId"); + + b.ToTable("ZombieSiegeParticipants", "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.ApiKey", b => + { + b.HasOne("Database.Entities.Alliance", "Alliance") + .WithOne("ApiKey") + .HasForeignKey("Database.Entities.ApiKey", "AllianceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Alliance"); + }); + + modelBuilder.Entity("Database.Entities.CustomEvent", b => + { + b.HasOne("Database.Entities.Alliance", "Alliance") + .WithMany("CustomEvents") + .HasForeignKey("AllianceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Database.Entities.CustomEventCategory", "CustomEventCategory") + .WithMany("CustomEvents") + .HasForeignKey("CustomEventCategoryId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Alliance"); + + b.Navigation("CustomEventCategory"); + }); + + modelBuilder.Entity("Database.Entities.CustomEventCategory", b => + { + b.HasOne("Database.Entities.Alliance", "Alliance") + .WithMany("CustomEventCategories") + .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.HasOne("Database.Entities.VsDuelLeague", "VsDuelLeague") + .WithMany("VsDuels") + .HasForeignKey("VsDuelLeagueId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Alliance"); + + b.Navigation("VsDuelLeague"); + }); + + 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("Database.Entities.ZombieSiege", b => + { + b.HasOne("Database.Entities.Alliance", "Alliance") + .WithMany("ZombieSieges") + .HasForeignKey("AllianceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Alliance"); + }); + + modelBuilder.Entity("Database.Entities.ZombieSiegeParticipant", b => + { + b.HasOne("Database.Entities.Player", "Player") + .WithMany("ZombieSiegeParticipants") + .HasForeignKey("PlayerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Database.Entities.ZombieSiege", "ZombieSiege") + .WithMany("ZombieSiegeParticipants") + .HasForeignKey("ZombieSiegeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Player"); + + b.Navigation("ZombieSiege"); + }); + + 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("ApiKey"); + + b.Navigation("CustomEventCategories"); + + b.Navigation("CustomEvents"); + + b.Navigation("DesertStorms"); + + b.Navigation("MarshalGuards"); + + b.Navigation("Players"); + + b.Navigation("Users"); + + b.Navigation("VsDuels"); + + b.Navigation("ZombieSieges"); + }); + + modelBuilder.Entity("Database.Entities.CustomEvent", b => + { + b.Navigation("CustomEventParticipants"); + }); + + modelBuilder.Entity("Database.Entities.CustomEventCategory", b => + { + b.Navigation("CustomEvents"); + }); + + 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"); + + b.Navigation("ZombieSiegeParticipants"); + }); + + modelBuilder.Entity("Database.Entities.Rank", b => + { + b.Navigation("Players"); + }); + + modelBuilder.Entity("Database.Entities.VsDuel", b => + { + b.Navigation("VsDuelParticipants"); + }); + + modelBuilder.Entity("Database.Entities.VsDuelLeague", b => + { + b.Navigation("VsDuels"); + }); + + modelBuilder.Entity("Database.Entities.ZombieSiege", b => + { + b.Navigation("ZombieSiegeParticipants"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Database/Migrations/20250417062713_Add_CustomEventCategory.cs b/Database/Migrations/20250417062713_Add_CustomEventCategory.cs new file mode 100644 index 0000000..c9f2b3d --- /dev/null +++ b/Database/Migrations/20250417062713_Add_CustomEventCategory.cs @@ -0,0 +1,93 @@ +// +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional + +namespace Database.Migrations +{ + /// + public partial class Add_CustomEventCategory : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "CustomEventCategoryId", + schema: "dbo", + table: "CustomEvents", + type: "uniqueidentifier", + nullable: true); + + migrationBuilder.CreateTable( + name: "CustomEventCategory", + schema: "dbo", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + AllianceId = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(255)", maxLength: 255, nullable: false), + IsPointsEvent = table.Column(type: "bit", nullable: false), + IsParticipationEvent = table.Column(type: "bit", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CustomEventCategory", x => x.Id); + table.ForeignKey( + name: "FK_CustomEventCategory_Alliances_AllianceId", + column: x => x.AllianceId, + principalSchema: "dbo", + principalTable: "Alliances", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_CustomEvents_CustomEventCategoryId", + schema: "dbo", + table: "CustomEvents", + column: "CustomEventCategoryId"); + + migrationBuilder.CreateIndex( + name: "IX_CustomEventCategory_AllianceId", + schema: "dbo", + table: "CustomEventCategory", + column: "AllianceId"); + + migrationBuilder.AddForeignKey( + name: "FK_CustomEvents_CustomEventCategory_CustomEventCategoryId", + schema: "dbo", + table: "CustomEvents", + column: "CustomEventCategoryId", + principalSchema: "dbo", + principalTable: "CustomEventCategory", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_CustomEvents_CustomEventCategory_CustomEventCategoryId", + schema: "dbo", + table: "CustomEvents"); + + migrationBuilder.DropTable( + name: "CustomEventCategory", + schema: "dbo"); + + migrationBuilder.DropIndex( + name: "IX_CustomEvents_CustomEventCategoryId", + schema: "dbo", + table: "CustomEvents"); + + migrationBuilder.DropColumn( + name: "CustomEventCategoryId", + schema: "dbo", + table: "CustomEvents"); + } + } +} diff --git a/Database/Migrations/20250417071312_Update_CustomEventCategory.Designer.cs b/Database/Migrations/20250417071312_Update_CustomEventCategory.Designer.cs new file mode 100644 index 0000000..12a2532 --- /dev/null +++ b/Database/Migrations/20250417071312_Update_CustomEventCategory.Designer.cs @@ -0,0 +1,1286 @@ +// +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("20250417071312_Update_CustomEventCategory")] + partial class Update_CustomEventCategory + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("dbo") + .HasAnnotation("ProductVersion", "9.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Database.Entities.Admonition", b => + { + b.Property("Id") + .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") + .HasColumnType("uniqueidentifier"); + + b.Property("Abbreviation") + .IsRequired() + .HasMaxLength(5) + .HasColumnType("nvarchar(5)"); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + 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.ApiKey", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("AllianceId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("EncryptedKey") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("ModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ModifiedOn") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("AllianceId") + .IsUnique(); + + b.ToTable("ApiKeys", "dbo"); + }); + + modelBuilder.Entity("Database.Entities.CustomEvent", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("AllianceId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("CustomEventCategoryId") + .HasColumnType("uniqueidentifier"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("EventDate") + .HasColumnType("datetime2"); + + b.Property("IsInProgress") + .HasColumnType("bit"); + + 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.HasIndex("CustomEventCategoryId"); + + b.ToTable("CustomEvents", "dbo"); + }); + + modelBuilder.Entity("Database.Entities.CustomEventCategory", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("AllianceId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsParticipationEvent") + .HasColumnType("bit"); + + b.Property("IsPointsEvent") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("AllianceId"); + + b.ToTable("CustomEventCategories", "dbo"); + }); + + modelBuilder.Entity("Database.Entities.CustomEventParticipant", b => + { + b.Property("Id") + .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") + .HasColumnType("uniqueidentifier"); + + b.Property("AllianceId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("EventDate") + .HasColumnType("datetime2"); + + b.Property("IsInProgress") + .HasColumnType("bit"); + + 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") + .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") + .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") + .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") + .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("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") + .HasColumnType("uniqueidentifier"); + + b.Property("AllianceId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("CreatedOn") + .HasColumnType("datetime2"); + + b.Property("DismissalReason") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("DismissedAt") + .HasColumnType("datetime2"); + + b.Property("IsDismissed") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + 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") + .HasColumnType("uniqueidentifier"); + + b.Property("AllianceId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("EventDate") + .HasColumnType("datetime2"); + + b.Property("IsInProgress") + .HasColumnType("bit"); + + 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("VsDuelLeagueId") + .HasColumnType("uniqueidentifier"); + + b.Property("Won") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("AllianceId"); + + b.HasIndex("VsDuelLeagueId"); + + b.ToTable("VsDuels", "dbo"); + }); + + modelBuilder.Entity("Database.Entities.VsDuelLeague", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Code") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.HasKey("Id"); + + b.ToTable("VsDuelLeagues", "dbo"); + + b.HasData( + new + { + Id = new Guid("01964298-2d6d-7f54-8e1f-ab23ec343378"), + Code = 1, + Name = "Silver League" + }, + new + { + Id = new Guid("01964298-2d6d-7620-bcc5-08103cbfd5de"), + Code = 2, + Name = "Gold League" + }, + new + { + Id = new Guid("01964298-2d6d-7e0b-b002-532458cbe7bd"), + Code = 3, + Name = "Diamond League" + }); + }); + + modelBuilder.Entity("Database.Entities.VsDuelParticipant", b => + { + b.Property("Id") + .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("Database.Entities.ZombieSiege", b => + { + b.Property("Id") + .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.HasKey("Id"); + + b.HasIndex("AllianceId"); + + b.ToTable("ZombieSieges", "dbo"); + }); + + modelBuilder.Entity("Database.Entities.ZombieSiegeParticipant", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("PlayerId") + .HasColumnType("uniqueidentifier"); + + b.Property("SurvivedWaves") + .HasColumnType("int"); + + b.Property("ZombieSiegeId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.HasIndex("ZombieSiegeId"); + + b.ToTable("ZombieSiegeParticipants", "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.ApiKey", b => + { + b.HasOne("Database.Entities.Alliance", "Alliance") + .WithOne("ApiKey") + .HasForeignKey("Database.Entities.ApiKey", "AllianceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Alliance"); + }); + + modelBuilder.Entity("Database.Entities.CustomEvent", b => + { + b.HasOne("Database.Entities.Alliance", "Alliance") + .WithMany("CustomEvents") + .HasForeignKey("AllianceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Database.Entities.CustomEventCategory", "CustomEventCategory") + .WithMany("CustomEvents") + .HasForeignKey("CustomEventCategoryId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Alliance"); + + b.Navigation("CustomEventCategory"); + }); + + modelBuilder.Entity("Database.Entities.CustomEventCategory", b => + { + b.HasOne("Database.Entities.Alliance", "Alliance") + .WithMany("CustomEventCategories") + .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.HasOne("Database.Entities.VsDuelLeague", "VsDuelLeague") + .WithMany("VsDuels") + .HasForeignKey("VsDuelLeagueId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Alliance"); + + b.Navigation("VsDuelLeague"); + }); + + 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("Database.Entities.ZombieSiege", b => + { + b.HasOne("Database.Entities.Alliance", "Alliance") + .WithMany("ZombieSieges") + .HasForeignKey("AllianceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Alliance"); + }); + + modelBuilder.Entity("Database.Entities.ZombieSiegeParticipant", b => + { + b.HasOne("Database.Entities.Player", "Player") + .WithMany("ZombieSiegeParticipants") + .HasForeignKey("PlayerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Database.Entities.ZombieSiege", "ZombieSiege") + .WithMany("ZombieSiegeParticipants") + .HasForeignKey("ZombieSiegeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Player"); + + b.Navigation("ZombieSiege"); + }); + + 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("ApiKey"); + + b.Navigation("CustomEventCategories"); + + b.Navigation("CustomEvents"); + + b.Navigation("DesertStorms"); + + b.Navigation("MarshalGuards"); + + b.Navigation("Players"); + + b.Navigation("Users"); + + b.Navigation("VsDuels"); + + b.Navigation("ZombieSieges"); + }); + + modelBuilder.Entity("Database.Entities.CustomEvent", b => + { + b.Navigation("CustomEventParticipants"); + }); + + modelBuilder.Entity("Database.Entities.CustomEventCategory", b => + { + b.Navigation("CustomEvents"); + }); + + 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"); + + b.Navigation("ZombieSiegeParticipants"); + }); + + modelBuilder.Entity("Database.Entities.Rank", b => + { + b.Navigation("Players"); + }); + + modelBuilder.Entity("Database.Entities.VsDuel", b => + { + b.Navigation("VsDuelParticipants"); + }); + + modelBuilder.Entity("Database.Entities.VsDuelLeague", b => + { + b.Navigation("VsDuels"); + }); + + modelBuilder.Entity("Database.Entities.ZombieSiege", b => + { + b.Navigation("ZombieSiegeParticipants"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Database/Migrations/20250417071312_Update_CustomEventCategory.cs b/Database/Migrations/20250417071312_Update_CustomEventCategory.cs new file mode 100644 index 0000000..071997d --- /dev/null +++ b/Database/Migrations/20250417071312_Update_CustomEventCategory.cs @@ -0,0 +1,128 @@ +// +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional + +namespace Database.Migrations +{ + /// + public partial class Update_CustomEventCategory : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_CustomEventCategory_Alliances_AllianceId", + schema: "dbo", + table: "CustomEventCategory"); + + migrationBuilder.DropForeignKey( + name: "FK_CustomEvents_CustomEventCategory_CustomEventCategoryId", + schema: "dbo", + table: "CustomEvents"); + + migrationBuilder.DropPrimaryKey( + name: "PK_CustomEventCategory", + schema: "dbo", + table: "CustomEventCategory"); + + migrationBuilder.RenameTable( + name: "CustomEventCategory", + schema: "dbo", + newName: "CustomEventCategories", + newSchema: "dbo"); + + migrationBuilder.RenameIndex( + name: "IX_CustomEventCategory_AllianceId", + schema: "dbo", + table: "CustomEventCategories", + newName: "IX_CustomEventCategories_AllianceId"); + + migrationBuilder.AddPrimaryKey( + name: "PK_CustomEventCategories", + schema: "dbo", + table: "CustomEventCategories", + column: "Id"); + + migrationBuilder.AddForeignKey( + name: "FK_CustomEventCategories_Alliances_AllianceId", + schema: "dbo", + table: "CustomEventCategories", + column: "AllianceId", + principalSchema: "dbo", + principalTable: "Alliances", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_CustomEvents_CustomEventCategories_CustomEventCategoryId", + schema: "dbo", + table: "CustomEvents", + column: "CustomEventCategoryId", + principalSchema: "dbo", + principalTable: "CustomEventCategories", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_CustomEventCategories_Alliances_AllianceId", + schema: "dbo", + table: "CustomEventCategories"); + + migrationBuilder.DropForeignKey( + name: "FK_CustomEvents_CustomEventCategories_CustomEventCategoryId", + schema: "dbo", + table: "CustomEvents"); + + migrationBuilder.DropPrimaryKey( + name: "PK_CustomEventCategories", + schema: "dbo", + table: "CustomEventCategories"); + + migrationBuilder.RenameTable( + name: "CustomEventCategories", + schema: "dbo", + newName: "CustomEventCategory", + newSchema: "dbo"); + + migrationBuilder.RenameIndex( + name: "IX_CustomEventCategories_AllianceId", + schema: "dbo", + table: "CustomEventCategory", + newName: "IX_CustomEventCategory_AllianceId"); + + migrationBuilder.AddPrimaryKey( + name: "PK_CustomEventCategory", + schema: "dbo", + table: "CustomEventCategory", + column: "Id"); + + migrationBuilder.AddForeignKey( + name: "FK_CustomEventCategory_Alliances_AllianceId", + schema: "dbo", + table: "CustomEventCategory", + column: "AllianceId", + principalSchema: "dbo", + principalTable: "Alliances", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_CustomEvents_CustomEventCategory_CustomEventCategoryId", + schema: "dbo", + table: "CustomEvents", + column: "CustomEventCategoryId", + principalSchema: "dbo", + principalTable: "CustomEventCategory", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + } + } +} diff --git a/Database/Migrations/ApplicationContextModelSnapshot.cs b/Database/Migrations/ApplicationContextModelSnapshot.cs index d9acc23..300c052 100644 --- a/Database/Migrations/ApplicationContextModelSnapshot.cs +++ b/Database/Migrations/ApplicationContextModelSnapshot.cs @@ -1,5 +1,4 @@ // -// using System; using Database; using Microsoft.EntityFrameworkCore; @@ -19,7 +18,7 @@ namespace Database.Migrations #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("dbo") - .HasAnnotation("ProductVersion", "9.0.0") + .HasAnnotation("ProductVersion", "9.0.4") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -140,6 +139,9 @@ namespace Database.Migrations .HasMaxLength(150) .HasColumnType("nvarchar(150)"); + b.Property("CustomEventCategoryId") + .HasColumnType("uniqueidentifier"); + b.Property("Description") .IsRequired() .HasMaxLength(500) @@ -173,9 +175,37 @@ namespace Database.Migrations b.HasIndex("AllianceId"); + b.HasIndex("CustomEventCategoryId"); + b.ToTable("CustomEvents", "dbo"); }); + modelBuilder.Entity("Database.Entities.CustomEventCategory", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("AllianceId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsParticipationEvent") + .HasColumnType("bit"); + + b.Property("IsPointsEvent") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("AllianceId"); + + b.ToTable("CustomEventCategories", "dbo"); + }); + modelBuilder.Entity("Database.Entities.CustomEventParticipant", b => { b.Property("Id") @@ -624,19 +654,19 @@ namespace Database.Migrations b.HasData( new { - Id = new Guid("0194d053-12b9-7818-9417-4d4eaa3b5ec1"), + Id = new Guid("01964298-2d6d-7f54-8e1f-ab23ec343378"), Code = 1, Name = "Silver League" }, new { - Id = new Guid("0194d053-12b9-7c64-9efb-e88745510c35"), + Id = new Guid("01964298-2d6d-7620-bcc5-08103cbfd5de"), Code = 2, Name = "Gold League" }, new { - Id = new Guid("0194d053-12b9-7217-a040-88ba1bbc7d69"), + Id = new Guid("01964298-2d6d-7e0b-b002-532458cbe7bd"), Code = 3, Name = "Diamond League" }); @@ -911,6 +941,24 @@ namespace Database.Migrations .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + b.HasOne("Database.Entities.CustomEventCategory", "CustomEventCategory") + .WithMany("CustomEvents") + .HasForeignKey("CustomEventCategoryId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Alliance"); + + b.Navigation("CustomEventCategory"); + }); + + modelBuilder.Entity("Database.Entities.CustomEventCategory", b => + { + b.HasOne("Database.Entities.Alliance", "Alliance") + .WithMany("CustomEventCategories") + .HasForeignKey("AllianceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + b.Navigation("Alliance"); }); @@ -1156,6 +1204,8 @@ namespace Database.Migrations { b.Navigation("ApiKey"); + b.Navigation("CustomEventCategories"); + b.Navigation("CustomEvents"); b.Navigation("DesertStorms"); @@ -1176,6 +1226,11 @@ namespace Database.Migrations b.Navigation("CustomEventParticipants"); }); + modelBuilder.Entity("Database.Entities.CustomEventCategory", b => + { + b.Navigation("CustomEvents"); + }); + modelBuilder.Entity("Database.Entities.DesertStorm", b => { b.Navigation("DesertStormParticipants"); diff --git a/README.md b/README.md index 6810e10..5c44f1b 100644 --- a/README.md +++ b/README.md @@ -17,15 +17,28 @@ All notable changes to this project are documented here. This project is currently in the **Beta Phase**. --- + +### **[0.9.0]** - *2025-04-23* +#### ✨ Added +- **Event Categories:** Custom events can now be assigned to specific categories. +- **Category-Based Leaderboards:** Each category can have its own leaderboard, depending on the event type (e.g., participation-only, points-only, or a combination). + +🛠️ **Fixed** +- Minor display issues in the event and leaderboard sections have been resolved. + +--- + ### **[0.8.3]** - *2025-04-10* #### ♻️ Changed - **NuGet Packages:** Updated all dependencies, including Serilog and Seq, to their latest stable versions. - **Logging Setup:** Cleaned up and reorganized the Serilog configuration for improved clarity and maintainability. ---- - 🛠️ **Fixed** - (N/A) + +--- + + ### **[0.8.0]** - *2025-03-11* #### ✨ Added - **Feedback Page:** Users can now submit feedback, including bug reports and feature requests. diff --git a/Ui/src/app/app.module.ts b/Ui/src/app/app.module.ts index 5897258..faba739 100644 --- a/Ui/src/app/app.module.ts +++ b/Ui/src/app/app.module.ts @@ -61,6 +61,9 @@ import { AllianceApiKeyComponent } from './pages/alliance/alliance-api-key/allia import { AllianceUserAdministrationComponent } from './pages/alliance/alliance-user-administration/alliance-user-administration.component'; import { FeedbackComponent } from './pages/feedback/feedback.component'; import { ImprintComponent } from './pages/imprint/imprint.component'; +import { CustomEventCategoryComponent } from './pages/custom-event/custom-event-category/custom-event-category.component'; +import { CustomEventLeaderboardComponent } from './pages/custom-event/custom-event-leaderboard/custom-event-leaderboard.component'; +import { CustomEventEventsComponent } from './pages/custom-event/custom-event-events/custom-event-events.component'; @NgModule({ declarations: [ @@ -111,7 +114,10 @@ import { ImprintComponent } from './pages/imprint/imprint.component'; AllianceApiKeyComponent, AllianceUserAdministrationComponent, FeedbackComponent, - ImprintComponent + ImprintComponent, + CustomEventCategoryComponent, + CustomEventLeaderboardComponent, + CustomEventEventsComponent ], imports: [ BrowserModule, diff --git a/Ui/src/app/models/customEvent.model.ts b/Ui/src/app/models/customEvent.model.ts index 0c189eb..0d1f31f 100644 --- a/Ui/src/app/models/customEvent.model.ts +++ b/Ui/src/app/models/customEvent.model.ts @@ -4,6 +4,8 @@ export interface CustomEventModel { id: string; allianceId: string; name: string; + categoryName?: string; + customEventCategoryId?: string; description: string; isPointsEvent: boolean; isParticipationEvent: boolean; @@ -25,5 +27,6 @@ export interface CreateCustomEventModel { isParticipationEvent: boolean; eventDate: string; allianceId: string; + customEventCategoryId?: string; isInProgress: boolean; } diff --git a/Ui/src/app/models/customEventCategory.model.ts b/Ui/src/app/models/customEventCategory.model.ts new file mode 100644 index 0000000..4c38113 --- /dev/null +++ b/Ui/src/app/models/customEventCategory.model.ts @@ -0,0 +1,21 @@ +export interface CustomEventCategoryModel { + id: string; + allianceId: string; + name: string; + isPointsEvent: boolean; + isParticipationEvent: boolean; +} + +export interface CreateCustomEventCategoryModel { + allianceId: string; + name: string; + isPointsEvent: boolean; + isParticipationEvent: boolean; +} + +export interface UpdateCustomEventCategoryModel { + id: string; + name: string; + isPointsEvent: boolean; + isParticipationEvent: boolean; +} diff --git a/Ui/src/app/models/customEventLeaderboard.model.ts b/Ui/src/app/models/customEventLeaderboard.model.ts new file mode 100644 index 0000000..3eda2e5 --- /dev/null +++ b/Ui/src/app/models/customEventLeaderboard.model.ts @@ -0,0 +1,16 @@ +export interface LeaderboardPointEventModel { + playerName: string; + points: number; +} + +export interface LeaderboardParticipationEventModel { + playerName: string; + participations: number; +} + +export interface LeaderboardPointAndParticipationEventModel { + playerName: string; + participations: number; + points: number; + totalPoints: number; +} diff --git a/Ui/src/app/pages/custom-event/custom-event-category/custom-event-category.component.css b/Ui/src/app/pages/custom-event/custom-event-category/custom-event-category.component.css new file mode 100644 index 0000000..e69de29 diff --git a/Ui/src/app/pages/custom-event/custom-event-category/custom-event-category.component.html b/Ui/src/app/pages/custom-event/custom-event-category/custom-event-category.component.html new file mode 100644 index 0000000..0eb01a0 --- /dev/null +++ b/Ui/src/app/pages/custom-event/custom-event-category/custom-event-category.component.html @@ -0,0 +1,94 @@ +@if (!isCreateCustomEventCategory) { +
+ +
+} + +@if (isCreateCustomEventCategory) { +
+ +
+ + + @if (f['name'].invalid && (f['name'].dirty || !f['name'].untouched)) { +
+ @if (f['name'].hasError('required')) { +

Name is required

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

Maximum 150 characters allowed

+ } +
+ } +
+ +
+ + +
+ + +
+ + +
+ +
+ + +
+
+} + +@if (!isCreateCustomEventCategory) { + @if (customEventCategories.length > 0) { +
+ + + + + + + + + + + @for (customEventCategory of customEventCategories | paginate: { id: 'customEventTable', itemsPerPage: 5, currentPage: pageNumber}; track customEventCategory.id) { + + + + + + + } + +
NamePoint eventParticipation eventAction
{{customEventCategory.name}} + + + + +
+ + +
+
+
+ +
+ + +
+ } @else { + + } +} diff --git a/Ui/src/app/pages/custom-event/custom-event-category/custom-event-category.component.ts b/Ui/src/app/pages/custom-event/custom-event-category/custom-event-category.component.ts new file mode 100644 index 0000000..17afdff --- /dev/null +++ b/Ui/src/app/pages/custom-event/custom-event-category/custom-event-category.component.ts @@ -0,0 +1,156 @@ +import {Component, inject, OnInit} from '@angular/core'; +import { + CreateCustomEventCategoryModel, + CustomEventCategoryModel, + UpdateCustomEventCategoryModel +} from "../../../models/customEventCategory.model"; +import {FormControl, FormGroup, Validators} from "@angular/forms"; +import {JwtTokenService} from "../../../services/jwt-token.service"; +import {CustomEventCategoryService} from "../../../services/custom-event-category.service"; +import {ToastrService} from "ngx-toastr"; +import Swal from "sweetalert2"; + +@Component({ + selector: 'app-custom-event-category', + templateUrl: './custom-event-category.component.html', + styleUrl: './custom-event-category.component.css' +}) +export class CustomEventCategoryComponent implements OnInit { + isCreateCustomEventCategory: any; + customEventCategories: CustomEventCategoryModel[] = []; + customEventCategoryForm!: FormGroup; + isUpdate: boolean = false; + private readonly _tokenService: JwtTokenService = inject(JwtTokenService); + private readonly _customEventCategoryService: CustomEventCategoryService = inject(CustomEventCategoryService); + private readonly _toastr: ToastrService = inject(ToastrService); + private allianceId: string = this._tokenService.getAllianceId()!; + pageNumber: number = 1; + + get f() { + return this.customEventCategoryForm.controls; + } + + ngOnInit() { + this.getCustomEventCategories(); + } + + getCustomEventCategories() { + this._customEventCategoryService.getAllianceCustomEventCategories(this.allianceId).subscribe({ + next: ((response) => { + if (response) { + this.customEventCategories = response; + } else { + this.customEventCategories = []; + } + }), + error: (error) => { + console.error(error); + } + }); + } + + onCreateCategory() { + this.createCustomEventCategoryForm(false); + } + + createCustomEventCategoryForm(isUpdate: boolean, customEventCategory: CustomEventCategoryModel | null = null) { + this.customEventCategoryForm = new FormGroup({ + id: new FormControl(isUpdate ? customEventCategory!.id : ''), + allianceId: new FormControl(this.allianceId), + name: new FormControl(isUpdate ? customEventCategory!.name : '', [Validators.required, Validators.maxLength(150)]), + isPointsEvent: new FormControl(isUpdate ? customEventCategory!.isPointsEvent : false), + isParticipationEvent: new FormControl(isUpdate ? customEventCategory!.isParticipationEvent : false), + }); + if (isUpdate) { + this.customEventCategoryForm.controls['isPointsEvent'].disable(); + this.customEventCategoryForm.controls['isParticipationEvent'].disable(); + } + this.isCreateCustomEventCategory = true; + } + + onCancel() { + this.isUpdate = false; + this.isCreateCustomEventCategory = false; + } + + onEditCustomEventCategory(customEventCategory: CustomEventCategoryModel) { + this.createCustomEventCategoryForm(true, customEventCategory); + } + + onDeleteCustomEventCategory(customEventCategory: CustomEventCategoryModel) { + Swal.fire({ + title: `Delete category: ${customEventCategory.name}`, + text: `Do you really want to delete the category? This will also delete all events associated with this category.`, + icon: "warning", + showCancelButton: true, + confirmButtonColor: "#3085d6", + cancelButtonColor: "#d33", + confirmButtonText: "Yes, delete it!" + }).then((result) => { + if (result.isConfirmed) { + this._customEventCategoryService.deleteCustomEventCategory(customEventCategory.id).subscribe({ + next: ((response) => { + if (response) { + Swal.fire({ + title: "Deleted!", + text: "Custom event has been deleted", + icon: "success" + }).then(_ => this.resetAndGetCustomEventCategories()); + } + }), + error: (error: Error) => { + console.log(error); + } + }); + } + }); + } + + onSubmit() { + if (this.customEventCategoryForm.invalid) { + return; + } + if (this.isUpdate) { + this.updateCustomEventCategory(); + return; + } + const customEventCategory: CreateCustomEventCategoryModel = this.customEventCategoryForm.value as CreateCustomEventCategoryModel; + this._customEventCategoryService.createCustomEventCategory(customEventCategory).subscribe({ + next: ((response) => { + if (response) { + this._toastr.success('Successfully created!', 'Successfully'); + this.onCancel(); + this.resetAndGetCustomEventCategories(); + } + }), + error: ((error: any) => { + console.error(error); + }) + }) + } + + pageChanged(event: number) { + this.pageNumber = event; + } + + updateCustomEventCategory() { + const customEventCategory: UpdateCustomEventCategoryModel = this.customEventCategoryForm.value as UpdateCustomEventCategoryModel; + this._customEventCategoryService.updateCustomEvent(customEventCategory.id, customEventCategory).subscribe({ + next: ((response) => { + if (response) { + this._toastr.success('Successfully updated!', 'Successfully'); + this.onCancel(); + this.resetAndGetCustomEventCategories(); + } + }), + error: ((error: any) => { + console.error(error); + }) + }) + } + + resetAndGetCustomEventCategories() { + this.pageNumber = 1; + this.getCustomEventCategories(); + } +} diff --git a/Ui/src/app/pages/custom-event/custom-event-detail/custom-event-detail.component.html b/Ui/src/app/pages/custom-event/custom-event-detail/custom-event-detail.component.html index 2f97aba..d763431 100644 --- a/Ui/src/app/pages/custom-event/custom-event-detail/custom-event-detail.component.html +++ b/Ui/src/app/pages/custom-event/custom-event-detail/custom-event-detail.component.html @@ -48,7 +48,7 @@
{{player.playerName}}
@if (customEventDetail.isPointsEvent) {
- Achieved Points: {{player.achievedPoints}} + Achieved Points: {{player.achievedPoints | number}}
} @if (customEventDetail.isParticipationEvent) { diff --git a/Ui/src/app/pages/custom-event/custom-event-events/custom-event-events.component.css b/Ui/src/app/pages/custom-event/custom-event-events/custom-event-events.component.css new file mode 100644 index 0000000..e69de29 diff --git a/Ui/src/app/pages/custom-event/custom-event-events/custom-event-events.component.html b/Ui/src/app/pages/custom-event/custom-event-events/custom-event-events.component.html new file mode 100644 index 0000000..078cfe1 --- /dev/null +++ b/Ui/src/app/pages/custom-event/custom-event-events/custom-event-events.component.html @@ -0,0 +1,176 @@ +@if (!isCreateCustomEvent) { +
+ +
+} + +@if (isCreateCustomEvent) { +
+ +
+ + + @if (f['eventDate'].invalid && (f['eventDate'].dirty || !f['eventDate'].untouched)) { +
+ @if (f['eventDate'].hasError('required')) { +

eventDate is required

+ } +
+ } +
+ +
+ + + @if (f['name'].invalid && (f['name'].dirty || !f['name'].untouched)) { +
+ @if (f['name'].hasError('required')) { +

Name is required

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

Maximum 150 characters allowed

+ } +
+ } +
+ +
+ + + @if (f['description'].invalid && (!f['description'].untouched || f['description'].dirty)) { +
+ @if (f['description'].hasError('required')) { +

Description is required

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

Maximum 500 characters allowed

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

Please add participants

+
+ } + +
+ +
+
+ + +
+
+} + +@if (!isCreateCustomEvent) { + @if (customEvents.length > 0) { +
+ + + + + + + + + + + + + + + @for (customEvent of customEvents | paginate: { id: 'customEventTable', itemsPerPage: pageSize, totalItems: totalRecord, currentPage: pageNumber}; track customEvent.id) { + + + + + + + + + + + } + +
Event DateNameCategoryPoint eventParticipation eventStatusCreatorAction
{{customEvent.eventDate | date: 'dd.MM.yyyy'}}{{customEvent.name}}{{customEvent.categoryName ?? ' - '}} + + + + + @if (customEvent.isInProgress) { + In Progress + } @else { + Done + } + {{customEvent.createdBy}} +
+ + + +
+
+
+ +
+ + + +
+ + Showing + {{ (pageNumber - 1) * pageSize + 1 }} - {{ pageNumber * pageSize > totalRecord ? totalRecord : pageNumber * pageSize }} + of {{ totalRecord }} results + +
+
+ } @else { + + } +} diff --git a/Ui/src/app/pages/custom-event/custom-event-events/custom-event-events.component.ts b/Ui/src/app/pages/custom-event/custom-event-events/custom-event-events.component.ts new file mode 100644 index 0000000..dfe3fd6 --- /dev/null +++ b/Ui/src/app/pages/custom-event/custom-event-events/custom-event-events.component.ts @@ -0,0 +1,312 @@ +import {Component, inject, OnInit} from '@angular/core'; +import {CreateCustomEventModel, CustomEventDetailModel, CustomEventModel} from "../../../models/customEvent.model"; +import {FormControl, FormGroup, Validators} from "@angular/forms"; +import {CustomEventParticipantModel} from "../../../models/customEventParticipant.model"; +import {CustomEventCategoryModel} from "../../../models/customEventCategory.model"; +import {JwtTokenService} from "../../../services/jwt-token.service"; +import {NgbModal} from "@ng-bootstrap/ng-bootstrap"; +import {CustomEventService} from "../../../services/custom-event.service"; +import {CustomEventParticipantService} from "../../../services/custom-event-participant.service"; +import {CustomEventCategoryService} from "../../../services/custom-event-category.service"; +import {Router} from "@angular/router"; +import {ToastrService} from "ngx-toastr"; +import { + CustomEventParticipantsModelComponent +} from "../../../modals/custom-event-participants-model/custom-event-participants-model.component"; +import Swal from "sweetalert2"; +import {forkJoin, Observable} from "rxjs"; + +@Component({ + selector: 'app-custom-event-events', + templateUrl: './custom-event-events.component.html', + styleUrl: './custom-event-events.component.css' +}) +export class CustomEventEventsComponent implements OnInit { + + customEvents: CustomEventModel[] = []; + customEventDetail!: CustomEventDetailModel; + isCreateCustomEvent: boolean = false; + customEventForm!: FormGroup; + playerSelected: boolean = false; + playerParticipated: { playerId: string, playerName: string, participated: boolean, achievedPoints: number }[] = []; + customEventParticipants: CustomEventParticipantModel[] = []; + customEventCategories: CustomEventCategoryModel[] = []; + isUpdate: boolean = false; + private readonly _tokenService: JwtTokenService = inject(JwtTokenService); + private readonly _modalService: NgbModal = inject(NgbModal); + private readonly _customEventService: CustomEventService = inject(CustomEventService); + private readonly _customEventParticipantService: CustomEventParticipantService = inject(CustomEventParticipantService); + private readonly _customEventCategoryService: CustomEventCategoryService = inject(CustomEventCategoryService); + private readonly _router: Router = inject(Router); + private readonly _toastr: ToastrService = inject(ToastrService); + private allianceId: string = this._tokenService.getAllianceId()!; + + public totalRecord: number = 0; + public pageNumber: number = 1; + public pageSize: number = 10 + + get f() { + return this.customEventForm.controls; + } + + ngOnInit() { + this.getCustomEvents(); + this.getCustomEventCategories(); + } + + getCustomEvents() { + this.customEvents = []; + this._customEventService.getAllianceCustomEvents(this.allianceId, this.pageNumber, this.pageSize).subscribe({ + next: ((response) => { + if (response) { + this.customEvents = response.data; + this.totalRecord = response.totalRecords; + } + }), + error: (error) => { + console.error(error); + } + }); + } + + getCustomEventCategories() { + this._customEventCategoryService.getAllianceCustomEventCategories(this.allianceId).subscribe({ + next: ((response) => { + if (response) { + this.customEventCategories = response; + } else { + this.customEventCategories = []; + } + }), + error: (error) => { + console.error(error); + } + }); + } + + + onCreateEvent() { + this.createCustomEventForm(false); + } + + + createCustomEventForm(isUpdate: boolean, customEventDetail: CustomEventDetailModel | null = null) { + if (isUpdate) { + this.playerSelected = true; + this.playerParticipated = [...customEventDetail!.customEventParticipants]; + } else { + this.playerSelected = false; + } + const d = isUpdate ? new Date(customEventDetail!.eventDate) : new Date(); + this.customEventForm = new FormGroup({ + id: new FormControl(isUpdate ? customEventDetail!.id : ''), + allianceId: new FormControl(this.allianceId), + customEventCategoryId: new FormControl(null), + name: new FormControl(isUpdate ? customEventDetail!.name : '', [Validators.required, Validators.maxLength(150)]), + description: new FormControl(isUpdate ? customEventDetail!.description : '', [Validators.required, Validators.maxLength(500)]), + isPointsEvent: new FormControl(isUpdate ? customEventDetail!.isPointsEvent : false), + isParticipationEvent: new FormControl(isUpdate ? customEventDetail!.isParticipationEvent : false), + eventDate: new FormControl(new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate())).toISOString().substring(0, 10)), + isInProgress: new FormControl(isUpdate ? customEventDetail!.isInProgress : true) + }); + this.isCreateCustomEvent = true; + if (isUpdate) { + this.customEventForm.controls['customEventCategoryId'].disable(); + } + } + + onAddParticipants() { + const modalRef = this._modalService.open(CustomEventParticipantsModelComponent, + {animation: true, backdrop: 'static', centered: true, size: 'lg', scrollable: true}); + if (this.playerSelected) { + this.playerParticipated.sort((a, b) => a.playerName.localeCompare(b.playerName)); + modalRef.componentInstance.players = [...this.playerParticipated]; + } + modalRef.componentInstance.allianceId = this.allianceId; + modalRef.componentInstance.isParticipationEvent = this.customEventForm.controls['isParticipationEvent'].value; + modalRef.componentInstance.isPointEvent = this.customEventForm.controls['isPointsEvent'].value; + modalRef.closed.subscribe({ + next: ((response: any[]) => { + if (response) { + this.playerSelected = true; + this.playerParticipated = [...response]; + this.playerParticipated.sort((a, b) => a.playerName.localeCompare(b.playerName)); + } + }) + }) + } + + onCancel() { + this.isUpdate = false; + this.isCreateCustomEvent = false; + } + + onSubmit() { + if (this.customEventForm.invalid) { + return; + } + if (this.isUpdate) { + this.updateCustomEvent(); + return; + } + const customEvent: CreateCustomEventModel = this.customEventForm.getRawValue() as CreateCustomEventModel; + this._customEventService.createCustomEvent(customEvent).subscribe({ + next: ((response) => { + if (response) { + this.insertCustomEventParticipants(response.id); + } + }), + error: ((error: any) => { + console.error(error); + }) + }) + } + + insertCustomEventParticipants(customEventId: string) { + const customEventParticipants: CustomEventParticipantModel[] = []; + this.playerParticipated.forEach((player) => { + const participant: CustomEventParticipantModel = { + id: '', + customEventId: customEventId, + participated: player.participated, + playerId: player.playerId, + achievedPoints: player.achievedPoints, + playerName: player.playerName + }; + customEventParticipants.push(participant); + }); + this._customEventParticipantService.insertCustomEventParticipants(customEventParticipants).subscribe({ + next: (() => { + this.playerParticipated = []; + this.onCancel(); + this.resetAndGetCustomEvents(); + }), + error: (error) => { + console.log(error); + } + }); + } + + onGoToCustomEventDetail(customEvent: CustomEventModel) { + this._router.navigate(['custom-event-detail', customEvent.id]).then(); + } + + onEditCustomEvent(customEvent: CustomEventModel) { + this._customEventService.getCustomEventDetail(customEvent.id).subscribe({ + next: ((response) => { + if (response) { + this.customEventParticipants = structuredClone(response.customEventParticipants); + this.isUpdate = true; + this.createCustomEventForm(true, response); + + this.customEventDetail = response; + } + }) + }); + } + + onDeleteCustomEvent(customEvent: CustomEventModel) { + Swal.fire({ + title: "Delete Custom event ?", + text: `Do you really want to delete the custom event`, + icon: "warning", + showCancelButton: true, + confirmButtonColor: "#3085d6", + cancelButtonColor: "#d33", + confirmButtonText: "Yes, delete it!" + }).then((result) => { + if (result.isConfirmed) { + this._customEventService.deleteCustomEvent(customEvent.id).subscribe({ + next: ((response) => { + if (response) { + Swal.fire({ + title: "Deleted!", + text: "Custom event has been deleted", + icon: "success" + }).then(_ => this.resetAndGetCustomEvents()); + } + }), + error: (error: Error) => { + console.log(error); + } + }); + } + }); + } + + private updateCustomEvent() { + const participantsToUpdate: any[] = []; + const customEvent: CustomEventModel = this.customEventForm.getRawValue() as CustomEventModel; + this.customEventParticipants.forEach((participant) => { + const player = this.playerParticipated.find(p => p.playerId === participant.playerId); + if (player!.participated !== participant.participated || player!.achievedPoints !== participant.achievedPoints) { + const playerToUpdate: CustomEventParticipantModel = { + playerId: player!.playerId, + achievedPoints: player!.achievedPoints, + playerName: player!.playerName, + participated: player!.participated, + id: participant.id, + customEventId: participant.customEventId, + } + participantsToUpdate.push(playerToUpdate); + } + }); + this._customEventService.updateCustomEvent(customEvent.id, customEvent).subscribe({ + next: ((response) => { + if (response) { + this.updateCustomEventParticipants(participantsToUpdate); + } + }) + }); + } + + private updateCustomEventParticipants(participantsToUpdate: any[]) { + if (participantsToUpdate.length <= 0) { + this._toastr.success('Successfully updated!', 'Successfully'); + this.onCancel(); + this.resetAndGetCustomEvents(); + return; + } + + const requests: Observable[] = []; + participantsToUpdate.forEach(participant => { + const request = this._customEventParticipantService.updateCustomEventParticipant(participant.id, participant); + requests.push(request); + }) + forkJoin(requests).subscribe({ + next: ((response) => { + if (response) { + this._toastr.success('Successfully updated!', 'Successfully'); + this.onCancel(); + this.resetAndGetCustomEvents(); + } + }) + }) + } + + pageChanged(event: number) { + this.pageNumber = event; + this.getCustomEvents(); + } + + resetAndGetCustomEvents() { + this.pageNumber = 1; + this.getCustomEvents(); + } + + onCategoryChange() { + const categoryId = this.customEventForm.get('customEventCategoryId')?.value; + if (categoryId !== null) { + const category = this.customEventCategories.find(e => e.id === categoryId); + this.customEventForm.controls['isPointsEvent'].setValue(category?.isPointsEvent); + this.customEventForm.controls['isPointsEvent'].disable(); + this.customEventForm.controls['isParticipationEvent'].setValue(category?.isParticipationEvent); + this.customEventForm.controls['isParticipationEvent'].disable(); + } else { + this.customEventForm.controls['isPointsEvent'].setValue(false); + this.customEventForm.controls['isParticipationEvent'].setValue(false); + this.customEventForm.controls['isPointsEvent'].enable(); + this.customEventForm.controls['isParticipationEvent'].enable(); + } + } +} diff --git a/Ui/src/app/pages/custom-event/custom-event-leaderboard/custom-event-leaderboard.component.css b/Ui/src/app/pages/custom-event/custom-event-leaderboard/custom-event-leaderboard.component.css new file mode 100644 index 0000000..e69de29 diff --git a/Ui/src/app/pages/custom-event/custom-event-leaderboard/custom-event-leaderboard.component.html b/Ui/src/app/pages/custom-event/custom-event-leaderboard/custom-event-leaderboard.component.html new file mode 100644 index 0000000..cd5f4d2 --- /dev/null +++ b/Ui/src/app/pages/custom-event/custom-event-leaderboard/custom-event-leaderboard.component.html @@ -0,0 +1,278 @@ +

🏆 Leaderboard

+ +
+ +
+ +@if (showInfo) { +
+
📊 How the Event Leaderboard is Calculated
+ +
+
+ +} +
+ + +
+ +@if (selectedCustomEventCategory) { +
+

Category: {{selectedCustomEventCategory.name}}

+

+ Point event: + +

+

+ Participation event: + +

+
+ +} + +@if (pointEventLeaderboard) { + @if (pointEventLeaderboard.length > 0) { +
+ + + + + + + + + @for (leader of pointEventLeaderboard | paginate: { id: 'pointEventTable', itemsPerPage: 8, currentPage: pointPageNumber}; track leader.playerName) { + + + + + } + +
PlayerTotal points
{{leader.playerName}}{{leader.points |number}}
+
+ + } @else { +

No events in category: {{selectedCustomEventCategory?.name}}

+ } +} + +@if (participationEventLeaderboard) { + @if (participationEventLeaderboard.length > 0) { +
+ + + + + + + + + @for (leader of participationEventLeaderboard | paginate: { id: 'participationEventTable', itemsPerPage: 8, currentPage: participationPageNumber}; track leader.playerName) { + + + + + } + +
PlayerTotal participations
{{leader.playerName}}{{leader.participations}}
+
+ + } @else { +

No events in category: {{selectedCustomEventCategory?.name}}

+ } +} + +@if (pointAndParticipationEventLeaderboard) { + @if (pointAndParticipationEventLeaderboard.length > 0) { +
+ + + + + + + + + + + @for (leader of pointAndParticipationEventLeaderboard | paginate: { id: 'pointAndParticipationEventTable', itemsPerPage: 8, currentPage: pointAndParticipationPageNumber}; track leader.playerName) { + + + + + + + } + +
PlayerScore pointsTotal participationsTotal points
{{leader.playerName}}{{leader.totalPoints}}{{leader.participations}}{{leader.points | number}}
+
+ + } @else { +

No events in category: {{selectedCustomEventCategory?.name}}

+ } +} diff --git a/Ui/src/app/pages/custom-event/custom-event-leaderboard/custom-event-leaderboard.component.ts b/Ui/src/app/pages/custom-event/custom-event-leaderboard/custom-event-leaderboard.component.ts new file mode 100644 index 0000000..58b55da --- /dev/null +++ b/Ui/src/app/pages/custom-event/custom-event-leaderboard/custom-event-leaderboard.component.ts @@ -0,0 +1,130 @@ +import {Component, inject, OnInit} from '@angular/core'; +import {CustomEventCategoryModel} from "../../../models/customEventCategory.model"; +import {CustomEventCategoryService} from "../../../services/custom-event-category.service"; +import {ToastrService} from "ngx-toastr"; +import {JwtTokenService} from "../../../services/jwt-token.service"; +import {CustomEventLeaderboardService} from "../../../services/custom-event-leaderboard.service"; +import { + LeaderboardParticipationEventModel, LeaderboardPointAndParticipationEventModel, + LeaderboardPointEventModel +} from "../../../models/customEventLeaderboard.model"; + +@Component({ + selector: 'app-custom-event-leaderboard', + templateUrl: './custom-event-leaderboard.component.html', + styleUrl: './custom-event-leaderboard.component.css' +}) +export class CustomEventLeaderboardComponent implements OnInit { + + private readonly _customEventCategoryService: CustomEventCategoryService = inject(CustomEventCategoryService); + private readonly _toastr: ToastrService = inject(ToastrService); + private readonly _tokenService: JwtTokenService = inject(JwtTokenService); + private readonly _customLeaderboardService: CustomEventLeaderboardService = inject(CustomEventLeaderboardService); + + private allianceId: string = this._tokenService.getAllianceId()!; + + customEventCategories: CustomEventCategoryModel[] = []; + selectedCustomEventCategory: CustomEventCategoryModel | undefined; + pointEventLeaderboard: LeaderboardPointEventModel[] | undefined; + participationEventLeaderboard: LeaderboardParticipationEventModel[] | undefined; + pointAndParticipationEventLeaderboard: LeaderboardPointAndParticipationEventModel[] | undefined; + pointPageNumber: number = 1; + participationPageNumber: number = 1; + pointAndParticipationPageNumber: number = 1; + showInfo: boolean = false; + activeTab: number = 1; + + ngOnInit() { + this.getCustomEventCategories(); + } + + getCustomEventCategories() { + this._customEventCategoryService.getAllianceCustomEventCategories(this.allianceId).subscribe({ + next: ((response) => { + if (response) { + this.customEventCategories = response; + } else { + this.customEventCategories = []; + } + }), + error: (error) => { + console.error(error); + } + }); + } + + onCategoryChange(event: any) { + this.pointEventLeaderboard = undefined; + this.participationEventLeaderboard = undefined; + this.pointAndParticipationEventLeaderboard = undefined; + const categoryId = event.target.value; + if (categoryId !== 'null') { + this.selectedCustomEventCategory = this.customEventCategories.find(e => e.id === categoryId); + if (this.selectedCustomEventCategory!.isPointsEvent && this.selectedCustomEventCategory!.isParticipationEvent) { + this.getPointAndParticipationEventLeaderboard(this.selectedCustomEventCategory!.id); + return; + } + if (this.selectedCustomEventCategory!.isParticipationEvent) { + this.getParticipationEventLeaderboard(this.selectedCustomEventCategory!.id); + return; + } + if (this.selectedCustomEventCategory!.isPointsEvent) { + this.getPointEventLeaderboard(this.selectedCustomEventCategory!.id); + } + } else { + this.selectedCustomEventCategory = undefined; + } + } + + getPointEventLeaderboard(customEventCategoryId: string) { + this._customLeaderboardService.getPointEvent(customEventCategoryId).subscribe({ + next: ((response) => { + if (response) { + this.pointEventLeaderboard = response; + } else { + this.pointEventLeaderboard = []; + } + }) + }) + } + + getParticipationEventLeaderboard(customEventCategoryId: string) { + this._customLeaderboardService.getParticipationEvent(customEventCategoryId).subscribe({ + next: ((response) => { + if (response) { + this.participationEventLeaderboard = response; + } else { + this.participationEventLeaderboard = []; + } + }) + }) + } + + getPointAndParticipationEventLeaderboard(customEventCategoryId: string) { + this._customLeaderboardService.getPointAndParticipationEvent(customEventCategoryId).subscribe({ + next: ((response) => { + if (response) { + this.pointAndParticipationEventLeaderboard = response; + } else { + this.pointAndParticipationEventLeaderboard = []; + } + }) + }) + } + + pointPageChanged(event: number) { + this.pointPageNumber = event; + } + + participationPageChanged(event: number) { + this.participationPageNumber = event; + } + + pointAndParticipationPageChanged(event: number) { + this.pointAndParticipationPageNumber = event; + } + + toggleInfo() { + this.showInfo = !this.showInfo; + } +} diff --git a/Ui/src/app/pages/custom-event/custom-event.component.html b/Ui/src/app/pages/custom-event/custom-event.component.html index 0f915e0..e58365f 100644 --- a/Ui/src/app/pages/custom-event/custom-event.component.html +++ b/Ui/src/app/pages/custom-event/custom-event.component.html @@ -1,169 +1,25 @@

Custom Event

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

eventDate is required

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

Name is required

- } - @if (f['name'].hasError('maxlength')) { -

Maximum 150 characters allowed

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

Description is required

- } - @if (f['description'].hasError('maxlength')) { -

Maximum 500 characters allowed

- } -
- } -
- -
- - -
- -
- - -
- -
- - -
- - - @if (!playerSelected) { -
-

Please add participants

-
- } - -
- -
-
- - -
-
- } - - @if (!isCreateCustomEvent) { - @if (customEvents.length > 0) { -
- - - - - - - - - - - - - - @for (customEvent of customEvents | paginate: { id: 'customEventTable', itemsPerPage: pageSize, totalItems: totalRecord, currentPage: pageNumber}; track customEvent.id) { - - - - - - - - - - } - -
Event DateNamePoint eventParticipation eventStatusCreatorAction
{{customEvent.eventDate | date: 'dd.MM.yyyy'}}{{customEvent.name}} - - - - - @if (customEvent.isInProgress) { - In Progress - } @else { - Done - } - {{customEvent.createdBy}} -
- - - -
-
-
- -
- - - -
- - Showing - {{ (pageNumber - 1) * pageSize + 1 }} - {{ pageNumber * pageSize > totalRecord ? totalRecord : pageNumber * pageSize }} - of {{ totalRecord }} results - -
-
- } @else { - - } - } - + +
diff --git a/Ui/src/app/pages/custom-event/custom-event.component.ts b/Ui/src/app/pages/custom-event/custom-event.component.ts index 4bd7efd..7c80e3e 100644 --- a/Ui/src/app/pages/custom-event/custom-event.component.ts +++ b/Ui/src/app/pages/custom-event/custom-event.component.ts @@ -1,272 +1,12 @@ -import {Component, inject, OnInit} from '@angular/core'; -import {FormControl, FormGroup, Validators} from "@angular/forms"; -import {JwtTokenService} from "../../services/jwt-token.service"; -import {CreateCustomEventModel, CustomEventDetailModel, CustomEventModel} from "../../models/customEvent.model"; -import {NgbModal} from "@ng-bootstrap/ng-bootstrap"; -import { - CustomEventParticipantsModelComponent -} from "../../modals/custom-event-participants-model/custom-event-participants-model.component"; -import {CustomEventService} from "../../services/custom-event.service"; -import {CustomEventParticipantModel} from "../../models/customEventParticipant.model"; -import {CustomEventParticipantService} from "../../services/custom-event-participant.service"; -import {Router} from "@angular/router"; -import Swal from "sweetalert2"; -import {ToastrService} from "ngx-toastr"; -import {forkJoin, Observable} from "rxjs"; +import {Component} from '@angular/core'; + @Component({ selector: 'app-custom-event', templateUrl: './custom-event.component.html', styleUrl: './custom-event.component.css' }) -export class CustomEventComponent implements OnInit { +export class CustomEventComponent { - customEvents: CustomEventModel[] = []; - customEventDetail!: CustomEventDetailModel; - isCreateCustomEvent: boolean = false; - customEventForm!: FormGroup; - playerSelected: boolean = false; - playerParticipated: { playerId: string, playerName: string, participated: boolean, achievedPoints: number }[] = []; - customEventParticipants: CustomEventParticipantModel[] = []; - isUpdate: boolean = false; - private readonly _tokenService: JwtTokenService = inject(JwtTokenService); - private readonly _modalService: NgbModal = inject(NgbModal); - private readonly _customEventService: CustomEventService = inject(CustomEventService); - private readonly _customEventParticipantService: CustomEventParticipantService = inject(CustomEventParticipantService); - private readonly _router: Router = inject(Router); - private readonly _toastr: ToastrService = inject(ToastrService); - private allianceId: string = this._tokenService.getAllianceId()!; - - public totalRecord: number = 0; - public pageNumber: number = 1; - public pageSize: number = 10 - - get f() { - return this.customEventForm.controls; - } - - ngOnInit() { - this.getCustomEvents(); - } - - getCustomEvents() { - this.customEvents = []; - this._customEventService.getAllianceCustomEvents(this.allianceId, this.pageNumber, this.pageSize).subscribe({ - next: ((response) => { - if (response) { - this.customEvents = response.data; - this.totalRecord = response.totalRecords; - } - }), - error: (error) => { - console.error(error); - } - }); - } - - - onCreateEvent() { - this.createCustomEventForm(false); - } - - - createCustomEventForm(isUpdate: boolean, customEventDetail: CustomEventDetailModel | null = null) { - if (isUpdate) { - this.playerSelected = true; - this.playerParticipated = [...customEventDetail!.customEventParticipants]; - } else { - this.playerSelected = false; - } - const d = isUpdate ? new Date(customEventDetail!.eventDate) : new Date(); - this.customEventForm = new FormGroup({ - id: new FormControl(isUpdate ? customEventDetail!.id : ''), - allianceId: new FormControl(this.allianceId), - name: new FormControl(isUpdate ? customEventDetail!.name : '', [Validators.required, Validators.maxLength(150)]), - description: new FormControl(isUpdate ? customEventDetail!.description : '', [Validators.required, Validators.maxLength(500)]), - isPointsEvent: new FormControl(isUpdate ? customEventDetail!.isPointsEvent : false), - isParticipationEvent: new FormControl(isUpdate ? customEventDetail!.isParticipationEvent : false), - eventDate: new FormControl(new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate())).toISOString().substring(0, 10)), - isInProgress: new FormControl(isUpdate ? customEventDetail!.isInProgress : true) - }); - this.isCreateCustomEvent = true; - } - - onAddParticipants() { - const modalRef = this._modalService.open(CustomEventParticipantsModelComponent, - {animation: true, backdrop: 'static', centered: true, size: 'lg', scrollable: true}); - if (this.playerSelected) { - this.playerParticipated.sort((a, b) => a.playerName.localeCompare(b.playerName)); - modalRef.componentInstance.players = [...this.playerParticipated]; - } - modalRef.componentInstance.allianceId = this.allianceId; - modalRef.componentInstance.isParticipationEvent = this.customEventForm.controls['isParticipationEvent'].value; - modalRef.componentInstance.isPointEvent = this.customEventForm.controls['isPointsEvent'].value; - modalRef.closed.subscribe({ - next: ((response: any[]) => { - if (response) { - this.playerSelected = true; - this.playerParticipated = [...response]; - this.playerParticipated.sort((a, b) => a.playerName.localeCompare(b.playerName)); - } - }) - }) - } - - onCancel() { - this.isUpdate = false; - this.isCreateCustomEvent = false; - } - - onSubmit() { - if (this.customEventForm.invalid) { - return; - } - if (this.isUpdate) { - this.updateCustomEvent(); - return; - } - const customEvent: CreateCustomEventModel = this.customEventForm.value as CreateCustomEventModel; - this._customEventService.createCustomEvent(customEvent).subscribe({ - next: ((response) => { - if (response) { - this.insertCustomEventParticipants(response.id); - } - }), - error: ((error: any) => { - console.error(error); - }) - }) - } - - insertCustomEventParticipants(customEventId: string) { - const customEventParticipants: CustomEventParticipantModel[] = []; - this.playerParticipated.forEach((player) => { - const participant: CustomEventParticipantModel = { - id: '', - customEventId: customEventId, - participated: player.participated, - playerId: player.playerId, - achievedPoints: player.achievedPoints, - playerName: player.playerName - }; - customEventParticipants.push(participant); - }); - this._customEventParticipantService.insertCustomEventParticipants(customEventParticipants).subscribe({ - next: (() => { - this.playerParticipated = []; - this.onCancel(); - this.resetAndGetCustomEvents(); - }), - error: (error) => { - console.log(error); - } - }); - } - - onGoToCustomEventDetail(customEvent: CustomEventModel) { - this._router.navigate(['custom-event-detail', customEvent.id]).then(); - } - - onEditCustomEvent(customEvent: CustomEventModel) { - this._customEventService.getCustomEventDetail(customEvent.id).subscribe({ - next: ((response) => { - if (response) { - this.customEventParticipants = structuredClone(response.customEventParticipants); - this.isUpdate = true; - this.createCustomEventForm(true, response); - - this.customEventDetail = response; - } - }) - }); - } - - onDeleteCustomEvent(customEvent: CustomEventModel) { - Swal.fire({ - title: "Delete Custom event ?", - text: `Do you really want to delete the custom event`, - icon: "warning", - showCancelButton: true, - confirmButtonColor: "#3085d6", - cancelButtonColor: "#d33", - confirmButtonText: "Yes, delete it!" - }).then((result) => { - if (result.isConfirmed) { - this._customEventService.deleteCustomEvent(customEvent.id).subscribe({ - next: ((response) => { - if (response) { - Swal.fire({ - title: "Deleted!", - text: "Custom event has been deleted", - icon: "success" - }).then(_ => this.resetAndGetCustomEvents()); - } - }), - error: (error: Error) => { - console.log(error); - } - }); - } - }); - } - - private updateCustomEvent() { - const participantsToUpdate: any[] = []; - const customEvent: CustomEventModel = this.customEventForm.value as CustomEventModel; - this.customEventParticipants.forEach((participant) => { - const player = this.playerParticipated.find(p => p.playerId === participant.playerId); - if (player!.participated !== participant.participated || player!.achievedPoints !== participant.achievedPoints) { - const playerToUpdate: CustomEventParticipantModel = { - playerId: player!.playerId, - achievedPoints: player!.achievedPoints, - playerName: player!.playerName, - participated: player!.participated, - id: participant.id, - customEventId: participant.customEventId, - } - participantsToUpdate.push(playerToUpdate); - } - }); - this._customEventService.updateCustomEvent(customEvent.id, customEvent).subscribe({ - next: ((response) => { - if (response) { - this.updateCustomEventParticipants(participantsToUpdate); - } - }) - }); - } - - private updateCustomEventParticipants(participantsToUpdate: any[]) { - if (participantsToUpdate.length <= 0) { - this._toastr.success('Successfully updated!', 'Successfully'); - this.onCancel(); - this.resetAndGetCustomEvents(); - return; - } - - const requests: Observable[] = []; - participantsToUpdate.forEach(participant => { - const request = this._customEventParticipantService.updateCustomEventParticipant(participant.id, participant); - requests.push(request); - }) - forkJoin(requests).subscribe({ - next: ((response) => { - if (response) { - this._toastr.success('Successfully updated!', 'Successfully'); - this.onCancel(); - this.resetAndGetCustomEvents(); - } - }) - }) - } - - pageChanged(event: number) { - this.pageNumber = event; - this.getCustomEvents(); - } - - resetAndGetCustomEvents() { - this.pageNumber = 1; - this.getCustomEvents(); - } + public activeTab: number = 1; } diff --git a/Ui/src/app/services/custom-event-category.service.ts b/Ui/src/app/services/custom-event-category.service.ts new file mode 100644 index 0000000..0a724ad --- /dev/null +++ b/Ui/src/app/services/custom-event-category.service.ts @@ -0,0 +1,38 @@ +import {inject, Injectable} from '@angular/core'; +import {environment} from "../../environments/environment"; +import {HttpClient} from "@angular/common/http"; +import {Observable} from "rxjs"; +import { + CreateCustomEventCategoryModel, + CustomEventCategoryModel, + UpdateCustomEventCategoryModel +} from "../models/customEventCategory.model"; + +@Injectable({ + providedIn: 'root' +}) +export class CustomEventCategoryService { + + private readonly _serviceUrl = environment.apiBaseUrl + 'customEventCategories/'; + private readonly _httpClient: HttpClient = inject(HttpClient); + + getAllianceCustomEventCategories(allianceId: string): Observable { + return this._httpClient.get(this._serviceUrl + 'Alliance/' + allianceId); + } + + getCustomEventCategory(customEventCategoryId: string): Observable { + return this._httpClient.get(this._serviceUrl + customEventCategoryId); + } + + createCustomEventCategory(customEventCategory: CreateCustomEventCategoryModel): Observable { + return this._httpClient.post(this._serviceUrl, customEventCategory); + } + + updateCustomEvent(customEventCategoryId: string, customEvent: UpdateCustomEventCategoryModel): Observable { + return this._httpClient.put(this._serviceUrl + customEventCategoryId, customEvent); + } + + deleteCustomEventCategory(customEventCategoryId: string): Observable { + return this._httpClient.delete(this._serviceUrl + customEventCategoryId ); + } +} diff --git a/Ui/src/app/services/custom-event-leaderboard.service.ts b/Ui/src/app/services/custom-event-leaderboard.service.ts new file mode 100644 index 0000000..355858a --- /dev/null +++ b/Ui/src/app/services/custom-event-leaderboard.service.ts @@ -0,0 +1,31 @@ +import {inject, Injectable} from '@angular/core'; +import {environment} from "../../environments/environment"; +import {HttpClient} from "@angular/common/http"; +import {Observable} from "rxjs"; +import { + LeaderboardParticipationEventModel, + LeaderboardPointAndParticipationEventModel, + LeaderboardPointEventModel +} from "../models/customEventLeaderboard.model"; + +@Injectable({ + providedIn: 'root' +}) +export class CustomEventLeaderboardService { + + private readonly _serviceUrl = environment.apiBaseUrl + 'customEventLeaderboards/'; + private readonly _httpClient: HttpClient = inject(HttpClient); + + getPointEvent(customEventCategoryId: string): Observable { + return this._httpClient.get(this._serviceUrl + 'point/' + customEventCategoryId); + } + + getParticipationEvent(customEventCategoryId: string): Observable { + return this._httpClient.get(this._serviceUrl + 'participation/' + customEventCategoryId); + } + + getPointAndParticipationEvent(customEventCategoryId: string): Observable { + return this._httpClient.get(this._serviceUrl + 'point-and-participation/' + customEventCategoryId); + + } +}