public async Task <ActionResult <MultiTimestampDto> > RejectAll(long characterId, long teamId, [FromBody] ApproveOrRejectAllListsDto dto)
    {
        var character = await _context.Characters.FindAsync(characterId);

        if (character is null)
        {
            return(NotFound());
        }

        var team = await _context.RaidTeams.FindAsync(teamId);

        if (team is null)
        {
            return(NotFound());
        }
        if (!await AuthorizeTeamAsync(team, AppPolicies.RaidLeaderOrAdmin))
        {
            return(Unauthorized());
        }
        if (character.TeamId.HasValue)
        {
            return(Problem("This action is only available for characters not on a raid team."));
        }
        if (character.Deactivated)
        {
            return(Problem("Character has been deactivated."));
        }

        var currentPhases = await GetCurrentPhasesAsync();

        var lootLists = await _context.CharacterLootLists.AsTracking()
                        .Where(ll => ll.CharacterId == characterId && currentPhases.Contains(ll.Phase))
                        .ToListAsync();

        if (!ValidateAllTimestamps(lootLists, dto.Timestamps, out byte invalidPhase))
        {
            return(Problem($"The loot list for phase {invalidPhase} has been changed. Refresh before trying again."));
        }

        var submissions = await _context.LootListTeamSubmissions
                          .AsTracking()
                          .Where(s => s.LootListCharacterId == characterId)
                          .ToListAsync();

        if (submissions.Find(s => s.TeamId == teamId) is null)
        {
            return(Unauthorized());
        }

        _context.LootListTeamSubmissions.RemoveRange(submissions.Where(s => s.TeamId == teamId));

        foreach (var list in lootLists)
        {
            if (!submissions.Any(s => s.TeamId != teamId && s.LootListPhase == list.Phase))
            {
                if (list.Status != LootListStatus.Locked)
                {
                    list.Status = LootListStatus.Editing;
                }

                list.ApprovedBy = null;
            }
        }

        await _context.SaveChangesAsync();

        TrackListStatusChange(lootLists);

        await NotifyOwnerAsync(character, team, approved : false, dto.Message);

        return(new MultiTimestampDto {
            Timestamps = lootLists.ToDictionary(ll => ll.Phase, ll => ll.Timestamp)
        });
    }
    public async Task <ActionResult <ApproveAllListsResponseDto> > ApproveAll(long characterId, long teamId, [FromBody] ApproveOrRejectAllListsDto dto)
    {
        var character = await _context.Characters.FindAsync(characterId);

        if (character is null)
        {
            return(NotFound());
        }

        var team = await _context.RaidTeams.FindAsync(teamId);

        if (team is null)
        {
            return(NotFound());
        }
        if (!await AuthorizeTeamAsync(team, AppPolicies.RaidLeaderOrAdmin))
        {
            return(Unauthorized());
        }
        if (character.TeamId.HasValue)
        {
            return(Problem("This action is only available for characters not on a raid team."));
        }
        if (character.Deactivated)
        {
            return(Problem("Character has been deactivated."));
        }

        var submittedPhases = dto.Timestamps.Keys.ToHashSet();
        var currentPhases   = await GetCurrentPhasesAsync();

        var lootLists = await _context.CharacterLootLists.AsTracking()
                        .Where(ll => ll.CharacterId == characterId && currentPhases.Contains(ll.Phase))
                        .ToListAsync();

        if (lootLists.Count != currentPhases.Count)
        {
            return(Problem("Character does not have a loot list for all current phases."));
        }
        if (!submittedPhases.SetEquals(currentPhases))
        {
            return(Problem("Request is missing one or more loot list timestamps."));
        }
        if (!ValidateAllTimestamps(lootLists, dto.Timestamps, out byte invalidPhase))
        {
            return(Problem($"The loot list for phase {invalidPhase} has been changed. Refresh before trying again."));
        }

        if (character.OwnerId > 0)
        {
            var existingCharacterName = await _context.Characters
                                        .AsNoTracking()
                                        .Where(c => c.OwnerId == character.OwnerId && c.TeamId == team.Id)
                                        .Select(c => c.Name)
                                        .FirstOrDefaultAsync();

            if (existingCharacterName?.Length > 0)
            {
                return(Problem($"The owner of this character is already on this team as {existingCharacterName}."));
            }
        }

        var submissions = await _context.LootListTeamSubmissions
                          .AsTracking()
                          .Where(s => s.LootListCharacterId == characterId)
                          .ToListAsync();

        if (submissions.Find(s => s.TeamId == teamId) is null)
        {
            return(Unauthorized());
        }

        _context.LootListTeamSubmissions.RemoveRange(submissions);

        character.TeamId       = teamId;
        character.MemberStatus = RaidMemberStatus.FullTrial;
        character.JoinedTeamAt = _serverTimeZoneInfo.TimeZoneNow();

        foreach (var list in lootLists)
        {
            list.ApprovedBy = User.GetDiscordId();
            if (list.Status != LootListStatus.Locked)
            {
                list.Status = LootListStatus.Approved;
            }
        }

        await _context.SaveChangesAsync();

        TrackListStatusChange(lootLists);

        await NotifyOwnerAsync(character, team, approved : true, dto.Message);

        return(new ApproveAllListsResponseDto
        {
            Timestamps = lootLists.ToDictionary(ll => ll.Phase, ll => ll.Timestamp),
            Member = await TryGetMemberDtoAsync(character, team)
        });
    }