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; } } }
/// <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 }); }