Beispiel #1
0
        private async Task SetupContext(int puzzleId)
        {
            Team = await UserEventHelper.GetTeamForPlayer(_context, Event, LoggedInUser);

            Puzzle = await _context.Puzzles.Where(
                (p) => p.ID == puzzleId).FirstOrDefaultAsync();

            PuzzleState = await(PuzzleStateHelper
                                .GetFullReadOnlyQuery(
                                    _context,
                                    Event,
                                    Puzzle,
                                    Team))
                          .FirstAsync();

            SubmissionViews = await(from submission in _context.Submissions
                                    join user in _context.PuzzleUsers on submission.Submitter equals user
                                    join r in _context.Responses on submission.Response equals r into responses
                                    from response in responses.DefaultIfEmpty()
                                    where submission.Team == Team &&
                                    submission.Puzzle == Puzzle
                                    orderby submission.TimeSubmitted
                                    select new SubmissionView()
            {
                Submission      = submission,
                Response        = response,
                SubmitterName   = user.Name,
                FreeformReponse = submission.FreeformResponse,
                IsFreeform      = Puzzle.IsFreeform
            }).ToListAsync();

            Submissions = new List <Submission>(SubmissionViews.Count);
            foreach (SubmissionView submissionView in SubmissionViews)
            {
                Submissions.Add(submissionView.Submission);
            }

            PuzzlesCausingGlobalLockout = await PuzzleStateHelper.PuzzlesCausingGlobalLockout(_context, Event, Team).ToListAsync();

            if (PuzzleState.SolvedTime != null)
            {
                if (!Puzzle.IsFreeform && Submissions?.Count > 0)
                {
                    AnswerToken = Submissions.Last().SubmissionText;
                }
                else
                {
                    AnswerToken = "(marked as solved by admin or author)";
                }
            }
        }
        private async Task SetupContext(int puzzleId)
        {
            Team = await UserEventHelper.GetTeamForPlayer(_context, Event, LoggedInUser);

            Puzzle = await _context.Puzzles.Where(
                (p) => p.ID == puzzleId).FirstOrDefaultAsync();

            PuzzleState = await(PuzzleStateHelper
                                .GetFullReadOnlyQuery(
                                    _context,
                                    Event,
                                    Puzzle,
                                    Team))
                          .FirstOrDefaultAsync();

            Submissions = await _context.Submissions.Where(
                (s) => s.Team == Team &&
                s.Puzzle == Puzzle)
                          .OrderBy(submission => submission.TimeSubmitted)
                          .ToListAsync();

            PuzzlesCausingGlobalLockout = await PuzzleStateHelper.PuzzlesCausingGlobalLockout(_context, Event, Team).ToListAsync();
        }
        public async Task OnGetAsync(SortOrder?sort, int teamId)
        {
            TeamID = teamId;
            Team myTeam = await UserEventHelper.GetTeamForPlayer(_context, Event, LoggedInUser);

            if (myTeam != null)
            {
                this.TeamID = myTeam.ID;
                await PuzzleStateHelper.CheckForTimedUnlocksAsync(_context, Event, myTeam);
            }
            else
            {
                throw new Exception("Not currently registered for a team");
            }
            this.Sort = sort;

            // all puzzles for this event that are real puzzles
            var puzzlesInEventQ = _context.Puzzles.Where(puzzle => puzzle.Event.ID == this.Event.ID && puzzle.IsPuzzle);

            // unless we're in a global lockout, then filter to those!
            var puzzlesCausingGlobalLockoutQ = PuzzleStateHelper.PuzzlesCausingGlobalLockout(_context, Event, myTeam);

            if (await puzzlesCausingGlobalLockoutQ.AnyAsync())
            {
                puzzlesInEventQ = puzzlesCausingGlobalLockoutQ;
            }

            // all puzzle states for this team that are unlocked (note: IsUnlocked bool is going to harm perf, just null check the time here)
            // Note that it's OK if some puzzles do not yet have a state record; those puzzles are clearly still locked and hence invisible.
            var stateForTeamQ = _context.PuzzleStatePerTeam.Where(state => state.TeamID == this.TeamID && state.UnlockedTime != null);

            // join 'em (note: just getting all properties for max flexibility, can pick and choose columns for perf later)
            // Note: EF gotcha is that you have to join into anonymous types in order to not lose valuable stuff
            var visiblePuzzlesQ = puzzlesInEventQ.Join(stateForTeamQ, (puzzle => puzzle.ID), (state => state.PuzzleID), (Puzzle, State) => new { Puzzle, State });

            switch (sort ?? DefaultSort)
            {
            case SortOrder.PuzzleAscending:
                visiblePuzzlesQ = visiblePuzzlesQ.OrderBy(puzzleWithState => puzzleWithState.Puzzle.Name);
                break;

            case SortOrder.PuzzleDescending:
                visiblePuzzlesQ = visiblePuzzlesQ.OrderByDescending(puzzleWithState => puzzleWithState.Puzzle.Name);
                break;

            case SortOrder.GroupAscending:
                visiblePuzzlesQ = visiblePuzzlesQ.OrderBy(puzzleWithState => puzzleWithState.Puzzle.Group).ThenBy(puzzleWithState => puzzleWithState.Puzzle.OrderInGroup);
                break;

            case SortOrder.GroupDescending:
                visiblePuzzlesQ = visiblePuzzlesQ.OrderByDescending(puzzleWithState => puzzleWithState.Puzzle.Group).ThenByDescending(puzzleWithState => puzzleWithState.Puzzle.OrderInGroup);
                break;

            case SortOrder.SolveAscending:
                visiblePuzzlesQ = visiblePuzzlesQ.OrderBy(puzzleWithState => puzzleWithState.State.SolvedTime ?? DateTime.MaxValue);
                break;

            case SortOrder.SolveDescending:
                visiblePuzzlesQ = visiblePuzzlesQ.OrderByDescending(puzzleWithState => puzzleWithState.State.SolvedTime ?? DateTime.MaxValue);
                break;

            default:
                throw new ArgumentException($"unknown sort: {sort}");
            }

            PuzzlesWithState = (await visiblePuzzlesQ.ToListAsync()).Select(x => new PuzzleWithState(x.Puzzle, x.State)).ToList();
        }
        public async Task OnGetAsync(SortOrder?sort, int teamId, PuzzleStateFilter?stateFilter)
        {
            TeamID = teamId;
            Team myTeam = await UserEventHelper.GetTeamForPlayer(_context, Event, LoggedInUser);

            if (myTeam != null)
            {
                this.TeamID = myTeam.ID;
                await PuzzleStateHelper.CheckForTimedUnlocksAsync(_context, Event, myTeam);
            }
            else
            {
                throw new Exception("Not currently registered for a team");
            }
            this.Sort        = sort;
            this.StateFilter = stateFilter;

            ShowAnswers   = Event.AnswersAvailableBegin <= DateTime.UtcNow;
            AllowFeedback = Event.AllowFeedback;

            // all puzzles for this event that are real puzzles
            var puzzlesInEventQ = _context.Puzzles.Where(puzzle => puzzle.Event.ID == this.Event.ID && puzzle.IsPuzzle);

            // unless we're in a global lockout, then filter to those!
            var puzzlesCausingGlobalLockoutQ = PuzzleStateHelper.PuzzlesCausingGlobalLockout(_context, Event, myTeam);

            if (await puzzlesCausingGlobalLockoutQ.AnyAsync())
            {
                puzzlesInEventQ = puzzlesCausingGlobalLockoutQ;
            }

            // all puzzle states for this team that are unlocked (note: IsUnlocked bool is going to harm perf, just null check the time here)
            // Note that it's OK if some puzzles do not yet have a state record; those puzzles are clearly still locked and hence invisible.
            // All puzzles will show if all answers have been released)
            var stateForTeamQ = _context.PuzzleStatePerTeam.Where(state => state.TeamID == this.TeamID && (ShowAnswers || state.UnlockedTime != null));

            // join 'em (note: just getting all properties for max flexibility, can pick and choose columns for perf later)
            // Note: EF gotcha is that you have to join into anonymous types in order to not lose valuable stuff
            var visiblePuzzlesQ = from Puzzle puzzle in puzzlesInEventQ
                                  join PuzzleStatePerTeam pspt in stateForTeamQ on puzzle.ID equals pspt.PuzzleID
                                  select new PuzzleView {
                ID = puzzle.ID, Group = puzzle.Group, OrderInGroup = puzzle.OrderInGroup, Name = puzzle.Name, CustomUrl = puzzle.CustomURL, Errata = puzzle.Errata, SolvedTime = pspt.SolvedTime, PieceMetaUsage = puzzle.PieceMetaUsage
            };

            switch (sort ?? DefaultSort)
            {
            case SortOrder.PuzzleAscending:
                visiblePuzzlesQ = visiblePuzzlesQ.OrderBy(pv => pv.Name);
                break;

            case SortOrder.PuzzleDescending:
                visiblePuzzlesQ = visiblePuzzlesQ.OrderByDescending(pv => pv.Name);
                break;

            case SortOrder.GroupAscending:
                visiblePuzzlesQ = visiblePuzzlesQ.OrderBy(pv => pv.Group).ThenBy(pv => pv.OrderInGroup).ThenBy(pv => pv.Name);
                break;

            case SortOrder.GroupDescending:
                visiblePuzzlesQ = visiblePuzzlesQ.OrderByDescending(pv => pv.Group).ThenByDescending(pv => pv.OrderInGroup).ThenByDescending(pv => pv.Name);
                break;

            case SortOrder.SolveAscending:
                visiblePuzzlesQ = visiblePuzzlesQ.OrderBy(pv => pv.SolvedTime ?? DateTime.MaxValue);
                break;

            case SortOrder.SolveDescending:
                visiblePuzzlesQ = visiblePuzzlesQ.OrderByDescending(pv => pv.SolvedTime ?? DateTime.MaxValue);
                break;

            default:
                throw new ArgumentException($"unknown sort: {sort}");
            }

            if (this.StateFilter == PuzzleStateFilter.Unsolved)
            {
                visiblePuzzlesQ = visiblePuzzlesQ.Where(puzzles => puzzles.SolvedTime == null);
            }

            PuzzleViews = await visiblePuzzlesQ.ToListAsync();

            Dictionary <int, ContentFile> files = await(from file in _context.ContentFiles
                                                        where file.Event == Event && file.FileType == ContentFileType.Puzzle
                                                        select file).ToDictionaryAsync(file => file.PuzzleID);

            foreach (var puzzleView in PuzzleViews)
            {
                files.TryGetValue(puzzleView.ID, out ContentFile content);
                puzzleView.Content = content;
            }

            if (ShowAnswers)
            {
                Dictionary <int, ContentFile> answers = await(from file in _context.ContentFiles
                                                              where file.Event == Event && file.FileType == ContentFileType.Answer
                                                              select file).ToDictionaryAsync(file => file.PuzzleID);

                foreach (var puzzleView in PuzzleViews)
                {
                    answers.TryGetValue(puzzleView.ID, out ContentFile answer);
                    puzzleView.Answer = answer;
                }
            }
        }
Beispiel #5
0
        /// <summary>
        /// Evaulates player submissions then either saves them to the database or returns an error to the caller
        /// </summary>
        public static async Task <SubmissionResponse> EvaluateSubmission(PuzzleServerContext context, PuzzleUser loggedInUser, Event thisEvent, int puzzleId, string submissionText, bool allowFreeformSharing)
        {
            //Query data needed to process submission
            Team team = await UserEventHelper.GetTeamForPlayer(context, thisEvent, loggedInUser);

            Puzzle puzzle = await context.Puzzles.Where(
                (p) => p.ID == puzzleId).FirstOrDefaultAsync();

            PuzzleStatePerTeam puzzleState = await(PuzzleStateHelper
                                                   .GetFullReadOnlyQuery(
                                                       context,
                                                       thisEvent,
                                                       puzzle,
                                                       team))
                                             .FirstAsync();

            List <Puzzle> puzzlesCausingGlobalLockout = await PuzzleStateHelper.PuzzlesCausingGlobalLockout(context, thisEvent, team).ToListAsync();

            // Return early for cases when there's obviously nothing we can do with the submission
            // The submission text is empty
            if (String.IsNullOrWhiteSpace(submissionText))
            {
                return(new SubmissionResponse()
                {
                    ResponseCode = SubmissionResponseCode.EmptySubmission
                });
            }

            // The puzzle is locked
            if (puzzle == null || puzzleState.UnlockedTime == null)
            {
                return(new SubmissionResponse()
                {
                    ResponseCode = SubmissionResponseCode.PuzzleLocked
                });
            }

            // The user or team isn't known
            if (loggedInUser == null || team == null)
            {
                return(new SubmissionResponse()
                {
                    ResponseCode = SubmissionResponseCode.Unauthorized
                });
            }

            // The event hasn't started yet
            if (DateTime.UtcNow < thisEvent.EventBegin)
            {
                return(new SubmissionResponse()
                {
                    ResponseCode = SubmissionResponseCode.Unauthorized
                });
            }

            // The team is locked out
            if (puzzleState.IsTeamLockedOut || puzzleState.IsEmailOnlyMode)
            {
                return(new SubmissionResponse()
                {
                    ResponseCode = SubmissionResponseCode.TeamLockedOut
                });
            }

            // The puzzle has already been solved
            if (puzzleState.SolvedTime != null)
            {
                return(new SubmissionResponse()
                {
                    ResponseCode = SubmissionResponseCode.AlreadySolved
                });
            }

            // The team is under a global lockout
            if (puzzlesCausingGlobalLockout.Count != 0 && !puzzlesCausingGlobalLockout.Contains(puzzle))
            {
                return(new SubmissionResponse()
                {
                    ResponseCode = SubmissionResponseCode.TeamLockedOut
                });
            }

            List <SubmissionView> submissionViews = await(from sub in context.Submissions
                                                          join user in context.PuzzleUsers on sub.Submitter equals user
                                                          join r in context.Responses on sub.Response equals r into responses
                                                          from response in responses.DefaultIfEmpty()
                                                          where sub.Team == team &&
                                                          sub.Puzzle == puzzle
                                                          orderby sub.TimeSubmitted
                                                          select new SubmissionView()
            {
                Submission      = sub,
                Response        = response,
                SubmitterName   = user.Name,
                FreeformReponse = sub.FreeformResponse,
                IsFreeform      = puzzle.IsFreeform
            }).ToListAsync();

            List <Submission> submissions = new List <Submission>(submissionViews.Count);

            foreach (SubmissionView submissionView in submissionViews)
            {
                submissions.Add(submissionView.Submission);
            }

            // The submission is a duplicate
            bool duplicateSubmission = (from sub in submissions
                                        where sub.SubmissionText == Response.FormatSubmission(submissionText)
                                        select sub).Any();

            if (duplicateSubmission)
            {
                return(new SubmissionResponse()
                {
                    ResponseCode = SubmissionResponseCode.DuplicateSubmission
                });
            }

            // Create submission and add it to list
            Submission submission = new Submission
            {
                TimeSubmitted        = DateTime.UtcNow,
                Puzzle               = puzzleState.Puzzle,
                Team                 = puzzleState.Team,
                Submitter            = loggedInUser,
                AllowFreeformSharing = allowFreeformSharing
            };

            string submissionTextToCheck = Response.FormatSubmission(submissionText);

            if (puzzle.IsFreeform)
            {
                submission.UnformattedSubmissionText = submissionText;
            }
            else
            {
                submission.SubmissionText = submissionText;
            }

            submission.Response = await context.Responses.Where(
                r => r.Puzzle.ID == puzzleId &&
                submissionTextToCheck == r.SubmittedText)
                                  .FirstOrDefaultAsync();

            submissions.Add(submission);

            // Update puzzle state if submission was correct
            if (submission.Response != null && submission.Response.IsSolution)
            {
                await PuzzleStateHelper.SetSolveStateAsync(context,
                                                           thisEvent,
                                                           submission.Puzzle,
                                                           submission.Team,
                                                           submission.TimeSubmitted);
            }
            else if (!puzzle.IsFreeform && submission.Response == null && thisEvent.IsAnswerSubmissionActive)
            {
                // We also determine if the puzzle should be set to email-only mode.
                if (IsPuzzleSubmissionLimitReached(
                        thisEvent,
                        submissions,
                        puzzleState))
                {
                    await PuzzleStateHelper.SetEmailOnlyModeAsync(context,
                                                                  thisEvent,
                                                                  submission.Puzzle,
                                                                  submission.Team,
                                                                  true);

                    var authors = await context.PuzzleAuthors.Where((pa) => pa.Puzzle == submission.Puzzle).Select((pa) => pa.Author.Email).ToListAsync();

                    MailHelper.Singleton.SendPlaintextBcc(authors,
                                                          $"{thisEvent.Name}: Team {submission.Team.Name} is in email mode for {submission.Puzzle.Name}",
                                                          "");
                }
                else
                {
                    // If the submission was incorrect and not a partial solution,
                    // we will do the lockout computations now.
                    DateTime?lockoutExpiryTime = ComputeLockoutExpiryTime(
                        thisEvent,
                        submissions,
                        puzzleState);

                    if (lockoutExpiryTime != null)
                    {
                        await PuzzleStateHelper.SetLockoutExpiryTimeAsync(context,
                                                                          thisEvent,
                                                                          submission.Puzzle,
                                                                          submission.Team,
                                                                          lockoutExpiryTime);
                    }
                }
            }

            context.Submissions.Add(submission);
            await context.SaveChangesAsync();

            // Send back responses for cases where the database has been updated
            // Correct response
            if (submission.Response != null && submission.Response.IsSolution)
            {
                return(new SubmissionResponse()
                {
                    ResponseCode = SubmissionResponseCode.Correct, CompleteResponse = submission.Response
                });
            }

            // Freeform response
            if (puzzle.IsFreeform)
            {
                return(new SubmissionResponse()
                {
                    ResponseCode = SubmissionResponseCode.Freeform, FreeformResponse = submission.FreeformResponse
                });
            }

            // Partial response
            if (submission.Response != null && !submission.Response.IsSolution)
            {
                return(new SubmissionResponse()
                {
                    ResponseCode = SubmissionResponseCode.Partial, CompleteResponse = submission.Response
                });
            }

            // Default to incorrect
            return(new SubmissionResponse()
            {
                ResponseCode = SubmissionResponseCode.Incorrect
            });
        }