Ejemplo n.º 1
0
        public async Task <ActionResult <TimestampDto> > PostSubmit(long characterId, byte phase, [FromBody] SubmitLootListDto dto, [FromServices] DiscordClientProvider dcp)
        {
            if (dto.SubmitTo.Count == 0)
            {
                ModelState.AddModelError(nameof(dto.SubmitTo), "Loot List must be submitted to at least one raid team.");
                return(ValidationProblem());
            }

            var character = await _context.Characters.FindAsync(characterId);

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

            if (character.Deactivated)
            {
                return(Problem("Character has been deactivated."));
            }

            var list = await _context.CharacterLootLists.FindAsync(characterId, phase);

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

            var auth = await _authorizationService.AuthorizeAsync(User, list.CharacterId, AppPolicies.CharacterOwnerOrAdmin);

            if (!auth.Succeeded)
            {
                return(Unauthorized());
            }

            if (!ValidateTimestamp(list, dto.Timestamp))
            {
                return(Problem("Loot list has been changed. Refresh before trying to update the status again."));
            }

            if (list.Status != LootListStatus.Editing && character.TeamId.HasValue)
            {
                return(Problem("Can't submit a list that is not editable."));
            }

            var teams = await _context.RaidTeams
                        .AsNoTracking()
                        .Where(t => dto.SubmitTo.Contains(t.Id))
                        .Select(t => new { t.Id, t.Name })
                        .ToDictionaryAsync(t => t.Id);

            if (teams.Count != dto.SubmitTo.Count)
            {
                return(Problem("One or more raid teams specified do not exist."));
            }

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

            foreach (var id in dto.SubmitTo)
            {
                if (submissions.Find(s => s.TeamId == id) is null)
                {
                    _context.LootListTeamSubmissions.Add(new()
                    {
                        LootListCharacterId = list.CharacterId,
                        LootListPhase       = list.Phase,
                        TeamId = id
                    });
                }
            }

            foreach (var submission in submissions)
            {
                if (!dto.SubmitTo.Contains(submission.TeamId))
                {
                    _context.LootListTeamSubmissions.Remove(submission);
                }
            }

            list.ApprovedBy = null;

            if (list.Status == LootListStatus.Editing)
            {
                list.Status = LootListStatus.Submitted;
            }

            await _context.SaveChangesAsync();

            _telemetry.TrackEvent("LootListStatusChanged", User, props =>
            {
                props["CharacterId"] = list.CharacterId.ToString();
                props["Phase"]       = list.Phase.ToString();
                props["Status"]      = list.Status.ToString();
                props["Method"]      = "Submit";
            });

            const string format = "You have a new application to {0} from {1}. ({2} {3})";

            await foreach (var claim in _context.UserClaims
                           .AsNoTracking()
                           .Where(claim => claim.ClaimType == AppClaimTypes.RaidLeader)
                           .Select(claim => new { claim.UserId, claim.ClaimValue })
                           .AsAsyncEnumerable())
            {
                if (long.TryParse(claim.ClaimValue, out var teamId) &&
                    teams.TryGetValue(teamId, out var team) &&
                    submissions.Find(s => s.TeamId == teamId) is null) // Don't notify when submission status doesn't change.
                {
                    await dcp.SendDmAsync(claim.UserId, m => m.WithContent(string.Format(
                                                                               format,
                                                                               team.Name,
                                                                               character.Name,
                                                                               character.Race.GetDisplayName(),
                                                                               list.MainSpec.GetDisplayName(true))));
                }
            }

            return(new TimestampDto {
                Timestamp = list.Timestamp
            });
        }
Ejemplo n.º 2
0
        public async Task <ActionResult <ApproveOrRejectLootListResponseDto> > PostApproveOrReject(long characterId, byte phase, [FromBody] ApproveOrRejectLootListDto dto, [FromServices] DiscordClientProvider dcp)
        {
            var character = await _context.Characters.FindAsync(characterId);

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

            var list = await _context.CharacterLootLists.FindAsync(characterId, phase);

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

            var team = await _context.RaidTeams.FindAsync(dto.TeamId);

            if (team is null)
            {
                ModelState.AddModelError(nameof(dto.TeamId), "Team does not exist.");
                return(ValidationProblem());
            }

            var auth = await _authorizationService.AuthorizeAsync(User, team.Id, AppPolicies.RaidLeaderOrAdmin);

            if (!auth.Succeeded)
            {
                return(Unauthorized());
            }

            if (!ValidateTimestamp(list, dto.Timestamp))
            {
                return(Problem("Loot list has been changed. Refresh before trying to update the status again."));
            }

            if (list.Status != LootListStatus.Submitted && character.TeamId.HasValue)
            {
                return(Problem("Can't approve or reject a list that isn't in the submitted state."));
            }

            var submissions = await _context.LootListTeamSubmissions
                              .AsTracking()
                              .Where(s => s.LootListCharacterId == list.CharacterId && s.LootListPhase == list.Phase)
                              .ToListAsync();

            var teamSubmission = submissions.Find(s => s.TeamId == dto.TeamId);

            if (teamSubmission is null)
            {
                return(Unauthorized());
            }

            MemberDto?member = null;

            if (dto.Approved)
            {
                _context.LootListTeamSubmissions.RemoveRange(submissions);

                if (character.TeamId.HasValue)
                {
                    if (character.TeamId != dto.TeamId)
                    {
                        return(Problem("That character is not assigned to the specified raid team."));
                    }
                }
                else
                {
                    var idString = character.Id.ToString();
                    var claim    = await _context.UserClaims.AsNoTracking()
                                   .Where(c => c.ClaimType == AppClaimTypes.Character && c.ClaimValue == idString)
                                   .Select(c => new { c.UserId })
                                   .FirstOrDefaultAsync();

                    if (claim is not null)
                    {
                        var otherClaims = await _context.UserClaims.AsNoTracking()
                                          .Where(c => c.ClaimType == AppClaimTypes.Character && c.UserId == claim.UserId)
                                          .Select(c => c.ClaimValue)
                                          .ToListAsync();

                        var characterIds = new List <long>();

                        foreach (var otherClaim in otherClaims)
                        {
                            if (long.TryParse(otherClaim, out var cid))
                            {
                                characterIds.Add(cid);
                            }
                        }

                        var existingCharacterName = await _context.Characters
                                                    .AsNoTracking()
                                                    .Where(c => characterIds.Contains(c.Id) && 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}."));
                        }
                    }

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

                list.ApprovedBy = User.GetDiscordId();

                if (list.Status != LootListStatus.Locked)
                {
                    list.Status = LootListStatus.Approved;
                }

                await _context.SaveChangesAsync();

                var characterQuery = _context.Characters.AsNoTracking().Where(c => c.Id == character.Id);
                var scope          = await _context.GetCurrentPriorityScopeAsync();

                foreach (var m in await HelperQueries.GetMembersAsync(_context, _serverTimeZoneInfo, characterQuery, scope, team.Id, team.Name, true))
                {
                    member = m;
                    break;
                }
            }
            else
            {
                _context.LootListTeamSubmissions.Remove(teamSubmission);

                if (character.TeamId == team.Id)
                {
                    character.TeamId = null;
                }

                if (submissions.Count == 1)
                {
                    if (list.Status != LootListStatus.Locked)
                    {
                        list.Status = LootListStatus.Editing;
                    }

                    list.ApprovedBy = null;
                }

                await _context.SaveChangesAsync();
            }

            _telemetry.TrackEvent("LootListStatusChanged", User, props =>
            {
                props["CharacterId"] = list.CharacterId.ToString();
                props["Phase"]       = list.Phase.ToString();
                props["Status"]      = list.Status.ToString();
                props["Method"]      = "ApproveOrReject";
            });

            var characterIdString = characterId.ToString();
            var owner             = await _context.UserClaims
                                    .AsNoTracking()
                                    .Where(c => c.ClaimType == AppClaimTypes.Character && c.ClaimValue == characterIdString)
                                    .Select(c => c.UserId)
                                    .FirstOrDefaultAsync();

            if (owner > 0)
            {
                var sb = new StringBuilder("Your application to ")
                         .Append(team.Name)
                         .Append(" for ")
                         .Append(character.Name)
                         .Append(" was ")
                         .Append(dto.Approved ? "approved!" : "rejected.");

                if (dto.Message?.Length > 0)
                {
                    sb.AppendLine()
                    .Append("<@")
                    .Append(User.GetDiscordId())
                    .AppendLine("> said:")
                    .Append("> ")
                    .Append(dto.Message);
                }

                await dcp.SendDmAsync(owner, m => m.WithContent(sb.ToString()));
            }

            return(new ApproveOrRejectLootListResponseDto {
                Timestamp = list.Timestamp, Member = member, LootListStatus = list.Status
            });
        }
    public async Task <ActionResult <MultiTimestampDto> > SubmitAll(long characterId, [FromBody] SubmitAllListsDto dto)
    {
        var character = await _context.Characters.FindAsync(characterId);

        if (character is null)
        {
            return(NotFound());
        }
        if (!await AuthorizeCharacterAsync(character, AppPolicies.CharacterOwnerOrAdmin))
        {
            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."));
        }
        if (dto.SubmitTo.Count == 0)
        {
            ModelState.AddModelError(nameof(dto.SubmitTo), "Loot List must be submitted to at least one raid team.");
            return(ValidationProblem());
        }

        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 (await _context.RaidTeams.AsNoTracking().CountAsync(t => dto.SubmitTo.Contains(t.Id)) != dto.SubmitTo.Count)
        {
            return(Problem("One or more raid teams specified do not exist."));
        }

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

        foreach (var id in dto.SubmitTo)
        {
            foreach (var phase in currentPhases)
            {
                if (submissions.Find(s => s.TeamId == id && s.LootListPhase == phase) is null)
                {
                    _context.LootListTeamSubmissions.Add(new()
                    {
                        LootListCharacterId = characterId,
                        LootListPhase       = phase,
                        TeamId = id
                    });
                }
            }
        }

        foreach (var submission in submissions)
        {
            if (!dto.SubmitTo.Contains(submission.TeamId))
            {
                _context.LootListTeamSubmissions.Remove(submission);
            }
        }

        foreach (var list in lootLists)
        {
            list.ApprovedBy = null;
            if (list.Status != LootListStatus.Locked)
            {
                list.Status = LootListStatus.Submitted;
            }
        }

        await _context.SaveChangesAsync();

        TrackListStatusChange(lootLists);

        await foreach (var leader in _context.RaidTeamLeaders
                       .AsNoTracking()
                       .Where(rtl => dto.SubmitTo.Contains(rtl.RaidTeamId))
                       .Select(rtl => new { rtl.UserId, rtl.RaidTeamId, TeamName = rtl.RaidTeam.Name })
                       .AsAsyncEnumerable())
        {
            if (submissions.Find(s => s.TeamId == leader.RaidTeamId) is null) // Don't notify on a resubmit.
            {
                await _discordClientProvider.SendDmAsync(
                    leader.UserId,
                    $"You have a new application to {leader.TeamName} from {character.Name}. ({character.Race.GetDisplayName()} {character.Class.GetDisplayName()})");
            }
        }

        return(new MultiTimestampDto {
            Timestamps = lootLists.ToDictionary(ll => ll.Phase, ll => ll.Timestamp)
        });
    }