Dismiss player function

This commit is contained in:
Tomasi - Developing 2025-01-16 16:25:21 +01:00
parent 2e113e5aae
commit efe9f9b8c3
21 changed files with 1533 additions and 19 deletions

View File

@ -33,8 +33,7 @@ namespace Api.Controllers.v1
}
[HttpGet("Alliance/{allianceId:guid}")]
public async Task<ActionResult<List<PlayerDto>>> GetAlliancePlayers(Guid allianceId,
CancellationToken cancellationToken)
public async Task<ActionResult<List<PlayerDto>>> GetAlliancePlayers(Guid allianceId, CancellationToken cancellationToken)
{
try
{
@ -54,6 +53,27 @@ namespace Api.Controllers.v1
}
}
[HttpGet("Alliance/dismiss/{allianceId:guid}")]
public async Task<ActionResult<List<PlayerDto>>> GetAllianceDismissPlayers(Guid allianceId, CancellationToken cancellationToken)
{
try
{
var allianceDismissPlayersResult =
await playerRepository.GetAllianceDismissPlayersAsync(allianceId, cancellationToken);
if (allianceDismissPlayersResult.IsFailure) return BadRequest(allianceDismissPlayersResult.Error);
return allianceDismissPlayersResult.Value.Count > 0
? Ok(allianceDismissPlayersResult.Value)
: NoContent();
}
catch (Exception e)
{
logger.LogError(e, e.Message);
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
[AllowAnonymous]
[HttpGet("[action]/{allianceId:guid}")]
public async Task<ActionResult<List<PlayerMvpDto>>> GetAllianceMvpPlayers(Guid allianceId,
@ -101,8 +121,7 @@ namespace Api.Controllers.v1
}
[HttpPost]
public async Task<ActionResult<PlayerDto>> CreatePlayer(CreatePlayerDto createPlayerDto,
CancellationToken cancellationToken)
public async Task<ActionResult<PlayerDto>> CreatePlayer(CreatePlayerDto createPlayerDto, CancellationToken cancellationToken)
{
try
{
@ -143,6 +162,51 @@ namespace Api.Controllers.v1
}
}
[HttpPut("{playerId:guid}/dismiss")]
public async Task<ActionResult<PlayerDto>> DismissPlayer(Guid playerId, DismissPlayerDto dismissPlayerDto,
CancellationToken cancellationToken)
{
try
{
if (!ModelState.IsValid) return UnprocessableEntity(ModelState);
if (playerId != dismissPlayerDto.Id) return Conflict(PlayerErrors.IdConflict);
var dismissResult = await playerRepository.DismissPlayerAsync(dismissPlayerDto, claimTypeService.GetFullName(User), cancellationToken);
return dismissResult.IsFailure
? BadRequest(dismissResult.Error)
: Ok(dismissResult.Value);
}
catch (Exception e)
{
logger.LogError(e, e.Message);
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
[HttpPut("{playerId:guid}/reactive")]
public async Task<ActionResult<PlayerDto>> ReactivePlayer(Guid playerId, ReactivatePlayerDto reactivatePlayerDto, CancellationToken cancellationToken)
{
try
{
if (!ModelState.IsValid) return UnprocessableEntity(ModelState);
if (playerId != reactivatePlayerDto.Id) return Conflict(PlayerErrors.IdConflict);
var reactiveResult = await playerRepository.ReactivatePlayerAsync(reactivatePlayerDto, claimTypeService.GetFullName(User), cancellationToken);
return reactiveResult.IsFailure
? BadRequest(reactiveResult.Error)
: Ok(reactiveResult.Value);
}
catch (Exception e)
{
logger.LogError(e, e.Message);
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
[HttpDelete("{playerId:guid}")]
public async Task<ActionResult<bool>> DeletePlayer(Guid playerId, CancellationToken cancellationToken)
{

View File

@ -0,0 +1,14 @@
using System.ComponentModel.DataAnnotations;
namespace Application.DataTransferObjects.Player;
public class DismissPlayerDto
{
[Required]
public Guid Id { get; set; }
[Required]
[MaxLength(255)]
public required string DismissalReason { get; set; }
}

View File

@ -1,6 +1,4 @@
using System.Runtime.InteropServices.JavaScript;
namespace Application.DataTransferObjects.Player;
namespace Application.DataTransferObjects.Player;
public class PlayerDto
{
@ -27,4 +25,10 @@ public class PlayerDto
public DateTime? ModifiedOn { get; set; }
public string? ModifiedBy { get; set; }
public bool IsDismissed { get; set; }
public DateTime? DismissedAt { get; set; }
public string? DismissalReason { get; set; }
}

View File

@ -0,0 +1,9 @@
using System.ComponentModel.DataAnnotations;
namespace Application.DataTransferObjects.Player;
public class ReactivatePlayerDto
{
[Required]
public Guid Id { get; set; }
}

View File

@ -9,6 +9,8 @@ public interface IPlayerRepository
Task<Result<List<PlayerDto>>> GetAlliancePlayersAsync(Guid allianceId, CancellationToken cancellationToken);
Task<Result<List<PlayerDto>>> GetAllianceDismissPlayersAsync(Guid allianceId, CancellationToken cancellationToken);
Task<Result<List<PlayerMvpDto>>> GetAlliancePlayersMvp(Guid allianceId, CancellationToken cancellationToken);
Task<Result<List<PlayerMvpDto>>> GetAllianceLeadershipMvp(Guid allianceId, CancellationToken cancellationToken);
@ -17,5 +19,9 @@ public interface IPlayerRepository
Task<Result<PlayerDto>> UpdatePlayerAsync(UpdatePlayerDto updatePlayerDto, string modifiedBy, CancellationToken cancellationToken);
Task<Result<PlayerDto>> DismissPlayerAsync(DismissPlayerDto dismissPlayerDto, string modifiedBy, CancellationToken cancellationToken);
Task<Result<PlayerDto>> ReactivatePlayerAsync(ReactivatePlayerDto reactivatePlayerDto, string modifiedBy, CancellationToken cancellationToken);
Task<Result<bool>> DeletePlayerAsync(Guid playerIId, CancellationToken cancellationToken);
}

View File

@ -15,6 +15,7 @@ public class PlayerProfile : Profile
CreateMap<CreatePlayerDto, Player>()
.ForMember(des => des.Id, opt => opt.MapFrom(src => Guid.CreateVersion7()))
.ForMember(des => des.IsDismissed, opt => opt.MapFrom(src => false))
.ForMember(des => des.CreatedOn, opt => opt.MapFrom(src => DateTime.Now));
CreateMap<UpdatePlayerDto, Player>()

View File

@ -36,6 +36,18 @@ public class PlayerRepository(ApplicationContext context, IMapper mapper, ILogge
return Result.Success(alliancePlayers);
}
public async Task<Result<List<PlayerDto>>> GetAllianceDismissPlayersAsync(Guid allianceId, CancellationToken cancellationToken)
{
var dismissAlliancePlayers = await context.Players
.IgnoreQueryFilters()
.ProjectTo<PlayerDto>(mapper.ConfigurationProvider)
.AsNoTracking()
.Where(player => player.AllianceId == allianceId && player.IsDismissed)
.ToListAsync(cancellationToken);
return Result.Success(dismissAlliancePlayers);
}
public async Task<Result<List<PlayerMvpDto>>> GetAlliancePlayersMvp(Guid allianceId, CancellationToken cancellationToken)
{
var currentDate = DateTime.Now;
@ -133,6 +145,7 @@ public class PlayerRepository(ApplicationContext context, IMapper mapper, ILogge
return playerMvps;
}
public async Task<Result<PlayerDto>> CreatePlayerAsync(CreatePlayerDto createPlayerDto, string createdBy, CancellationToken cancellationToken)
{
var newPlayer = mapper.Map<Player>(createPlayerDto);
@ -174,9 +187,78 @@ public class PlayerRepository(ApplicationContext context, IMapper mapper, ILogge
}
}
public async Task<Result<PlayerDto>> DismissPlayerAsync(DismissPlayerDto dismissPlayerDto, string modifiedBy, CancellationToken cancellationToken)
{
var playerToDismiss = await context.Players
.FirstOrDefaultAsync(player => player.Id == dismissPlayerDto.Id, cancellationToken);
if (playerToDismiss is null) return Result.Failure<PlayerDto>(PlayerErrors.NotFound);
playerToDismiss.IsDismissed = true;
playerToDismiss.DismissedAt = DateTime.Now;
playerToDismiss.DismissalReason = dismissPlayerDto.DismissalReason;
playerToDismiss.ModifiedOn = DateTime.Now;
playerToDismiss.ModifiedBy = modifiedBy;
playerToDismiss.Admonitions.Add(new Admonition()
{
CreatedBy = modifiedBy,
Reason = $"Player was dismiss from the alliance by {modifiedBy}. Reason: {dismissPlayerDto.DismissalReason}",
CreatedOn = DateTime.Now,
PlayerId = playerToDismiss.Id,
Id = Guid.CreateVersion7()
});
try
{
await context.SaveChangesAsync(cancellationToken);
return Result.Success(mapper.Map<PlayerDto>(playerToDismiss));
}
catch (Exception e)
{
logger.LogError(e, e.Message);
return Result.Failure<PlayerDto>(GeneralErrors.DatabaseError);
}
}
public async Task<Result<PlayerDto>> ReactivatePlayerAsync(ReactivatePlayerDto reactivatePlayerDto, string modifiedBy, CancellationToken cancellationToken)
{
var playerToReactivate = await context.Players
.IgnoreQueryFilters()
.FirstOrDefaultAsync(player => player.Id == reactivatePlayerDto.Id, cancellationToken);
if (playerToReactivate is null) return Result.Failure<PlayerDto>(PlayerErrors.NotFound);
playerToReactivate.IsDismissed = false;
playerToReactivate.DismissedAt = null;
playerToReactivate.DismissalReason = null;
playerToReactivate.ModifiedOn = DateTime.Now;
playerToReactivate.ModifiedBy = modifiedBy;
playerToReactivate.Notes.Add(new Note()
{
CreatedBy = modifiedBy,
PlayerNote = $"Player was accepted back into the alliance by {modifiedBy}",
CreatedOn = DateTime.Now,
Id = Guid.CreateVersion7()
});
try
{
await context.SaveChangesAsync(cancellationToken);
return Result.Success(mapper.Map<PlayerDto>(playerToReactivate));
}
catch (Exception e)
{
logger.LogError(e, e.Message);
return Result.Failure<PlayerDto>(GeneralErrors.DatabaseError);
}
}
public async Task<Result<bool>> DeletePlayerAsync(Guid playerIId, CancellationToken cancellationToken)
{
var playerToDelete = await context.Players
.IgnoreQueryFilters()
.FirstOrDefaultAsync(player => player.Id == playerIId, cancellationToken);
if (playerToDelete is null) return Result.Failure<bool>(PlayerErrors.NotFound);

View File

@ -17,6 +17,11 @@ public class PlayerConfiguration : IEntityTypeConfiguration<Player>
builder.Property(player => player.CreatedBy).IsRequired().HasMaxLength(150);
builder.Property(player => player.ModifiedOn).IsRequired(false);
builder.Property(player => player.ModifiedBy).IsRequired(false).HasMaxLength(150);
builder.Property(player => player.IsDismissed).IsRequired().HasDefaultValue(false);
builder.Property(player => player.DismissedAt).IsRequired(false);
builder.Property(player => player.DismissalReason).IsRequired(false).HasMaxLength(255);
builder.HasQueryFilter(player => !player.IsDismissed);
builder.HasOne(player => player.Alliance)
.WithMany(alliance => alliance.Players)

View File

@ -22,6 +22,12 @@ public class Player : BaseEntity
public string? ModifiedBy { get; set; }
public bool IsDismissed { get; set; }
public DateTime? DismissedAt { get; set; }
public string? DismissalReason { get; set; }
public ICollection<DesertStormParticipant> DesertStormParticipants { get; set; } = [];
public ICollection<VsDuelParticipant> VsDuelParticipants { get; set; } = [];

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,88 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
namespace Database.Migrations
{
/// <inheritdoc />
public partial class AddDismissToPlayer : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "DismissalReason",
schema: "dbo",
table: "Players",
type: "nvarchar(255)",
maxLength: 255,
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "DismissedAt",
schema: "dbo",
table: "Players",
type: "datetime2",
nullable: true);
migrationBuilder.AddColumn<bool>(
name: "IsDismissed",
schema: "dbo",
table: "Players",
type: "bit",
nullable: false,
defaultValue: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DeleteData(
schema: "dbo",
table: "VsDuelLeagues",
keyColumn: "Id",
keyValue: new Guid("01946f11-c5f1-750f-b3f5-61ec7a00f837"));
migrationBuilder.DeleteData(
schema: "dbo",
table: "VsDuelLeagues",
keyColumn: "Id",
keyValue: new Guid("01946f11-c5f1-7576-b861-14df423f92f2"));
migrationBuilder.DeleteData(
schema: "dbo",
table: "VsDuelLeagues",
keyColumn: "Id",
keyValue: new Guid("01946f11-c5f1-771e-8600-331582290457"));
migrationBuilder.DropColumn(
name: "DismissalReason",
schema: "dbo",
table: "Players");
migrationBuilder.DropColumn(
name: "DismissedAt",
schema: "dbo",
table: "Players");
migrationBuilder.DropColumn(
name: "IsDismissed",
schema: "dbo",
table: "Players");
migrationBuilder.InsertData(
schema: "dbo",
table: "VsDuelLeagues",
columns: new[] { "Id", "Code", "Name" },
values: new object[,]
{
{ new Guid("01938bf2-cf1b-742f-b3d6-824dbed7bf25"), 1, "Silver League" },
{ new Guid("01938bf2-cf1b-7a69-8607-87b1987a19b0"), 2, "Gold League" },
{ new Guid("01938bf2-cf1b-7cde-961e-e8cb35890551"), 3, "Diamond League" }
});
}
}
}

View File

@ -355,6 +355,18 @@ namespace Database.Migrations
b.Property<DateTime>("CreatedOn")
.HasColumnType("datetime2");
b.Property<string>("DismissalReason")
.HasMaxLength(255)
.HasColumnType("nvarchar(255)");
b.Property<DateTime?>("DismissedAt")
.HasColumnType("datetime2");
b.Property<bool>("IsDismissed")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false);
b.Property<int>("Level")
.HasColumnType("int");
@ -576,19 +588,19 @@ namespace Database.Migrations
b.HasData(
new
{
Id = new Guid("01938bf2-cf1b-742f-b3d6-824dbed7bf25"),
Id = new Guid("01946f11-c5f1-771e-8600-331582290457"),
Code = 1,
Name = "Silver League"
},
new
{
Id = new Guid("01938bf2-cf1b-7a69-8607-87b1987a19b0"),
Id = new Guid("01946f11-c5f1-7576-b861-14df423f92f2"),
Code = 2,
Name = "Gold League"
},
new
{
Id = new Guid("01938bf2-cf1b-7cde-961e-e8cb35890551"),
Id = new Guid("01946f11-c5f1-750f-b3f5-61ec7a00f837"),
Code = 3,
Name = "Diamond League"
});

View File

@ -22,9 +22,11 @@ import {CustomEventComponent} from "./pages/custom-event/custom-event.component"
import {ZombieSiegeComponent} from "./pages/zombie-siege/zombie-siege.component";
import {ZombieSiegeDetailComponent} from "./pages/zombie-siege/zombie-siege-detail/zombie-siege-detail.component";
import {CustomEventDetailComponent} from "./pages/custom-event/custom-event-detail/custom-event-detail.component";
import {DismissPlayerComponent} from "./pages/dismiss-player/dismiss-player.component";
const routes: Routes = [
{path: 'players', component: PlayerComponent, canActivate: [authGuard]},
{path: 'dismiss-players', component: DismissPlayerComponent, canActivate: [authGuard]},
{path: 'player-information/:id', component: PlayerInformationComponent, canActivate: [authGuard]},
{path: 'marshal-guard', component: MarshalGuardComponent, canActivate: [authGuard]},
{path: 'marshal-guard-detail/:id', component: MarshalGuardDetailComponent, canActivate: [authGuard]},

View File

@ -52,6 +52,7 @@ import { ZombieSiegeParticipantsModalComponent } from './modals/zombie-siege-par
import { ZombieSiegeDetailComponent } from './pages/zombie-siege/zombie-siege-detail/zombie-siege-detail.component';
import { CustomEventParticipantsModelComponent } from './modals/custom-event-participants-model/custom-event-participants-model.component';
import { CustomEventDetailComponent } from './pages/custom-event/custom-event-detail/custom-event-detail.component';
import { DismissPlayerComponent } from './pages/dismiss-player/dismiss-player.component';
@NgModule({
declarations: [
@ -94,7 +95,8 @@ import { CustomEventDetailComponent } from './pages/custom-event/custom-event-de
ZombieSiegeParticipantsModalComponent,
ZombieSiegeDetailComponent,
CustomEventParticipantsModelComponent,
CustomEventDetailComponent
CustomEventDetailComponent,
DismissPlayerComponent
],
imports: [
BrowserModule,

View File

@ -25,6 +25,11 @@
</a>
</li>
<li class="nav-item">
<a (click)="isShown = false" class="nav-link" routerLink="/dismiss-players" routerLinkActive="active">Dismiss Players
</a>
</li>
<li class="nav-item">
<a (click)="isShown = false" class="nav-link" routerLink="/marshal-guard" routerLinkActive="active">Marshal Guard
</a>

View File

@ -0,0 +1,5 @@
<div class="container mt-3 pb-5">
<h2 class="text-center">Dismiss Players</h2>
<app-under-development></app-under-development>
</div>

View File

@ -0,0 +1,10 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-dismiss-player',
templateUrl: './dismiss-player.component.html',
styleUrl: './dismiss-player.component.css'
})
export class DismissPlayerComponent {
}

View File

@ -66,7 +66,7 @@
<td>
<div class="d-flex gap-3 justify-content-around">
<i (click)="onEditPlayer(player)" class="bi custom-edit-icon bi-pencil-fill"></i>
<i (click)="onDeletePlayer(player)" class="bi custom-delete-icon bi-trash3"></i>
<i (click)="onDismissPlayer(player)" class="bi custom-delete-icon bi-person-dash"></i>
</div>
</td>
</tr>

View File

@ -109,23 +109,32 @@ export class PlayerComponent implements OnInit {
})
}
onDeletePlayer(player: PlayerModel) {
onDismissPlayer(player: PlayerModel) {
Swal.fire({
title: "Delete Player ?",
text: `Do you really want to delete the player ${player.playerName}`,
title: `Dismiss Player ${player.playerName}?`,
text: `The player will not be permanently deleted but will instead be moved to the "Dismissed Players" section for further reference.`,
icon: "warning",
input: "textarea",
inputPlaceholder:"Enter the reason for dismissal...",
showCancelButton: true,
confirmButtonColor: "#3085d6",
cancelButtonColor: "#d33",
confirmButtonText: "Yes, delete it!"
confirmButtonText: "Yes, dismiss it!",
preConfirm: (reason: string) => {
if (!reason) {
Swal.showValidationMessage("Please provide a reason for dismissal.")
}
return reason;
}
}).then((result) => {
if (result.isConfirmed) {
this._playerService.deletePlayer(player.id).subscribe({
const reason = result.value;
this._playerService.dismissPlayer(player.id, reason).subscribe({
next: ((response) => {
if (response) {
Swal.fire({
title: "Deleted!",
text: "Player has been deleted",
title: "Dismissed!",
text: `Player has been dismissed for the following reason: "${reason}"`,
icon: "success"
}).then(_ => this.getPlayers(this.allianceId!));
}

View File

@ -24,6 +24,14 @@ export class PlayerService {
return this._httpClient.put<PlayerModel>(this._serviceUrl + playerId, player);
}
public dismissPlayer(playerId: string, reason: string): Observable<PlayerModel> {
const dismissRequest = {
id: playerId,
dismissalReason: reason
};
return this._httpClient.put<PlayerModel>(this._serviceUrl + playerId + '/dismiss', dismissRequest);
}
public insertPlayer(player: CreatePlayerModel): Observable<PlayerModel> {
return this._httpClient.post<PlayerModel>(this._serviceUrl, player);
}