/// <summary>
        /// Sets the MayBeAdminOrAuthor bit for all admins and authors of all events
        /// </summary>
        public async Task <IActionResult> OnPostMigrateAdminsAsync()
        {
            using (var transaction = await _context.Database.BeginTransactionAsync(IsolationLevel.Serializable))
            {
                List <PuzzleUser> allAdmins = await(from admin in _context.EventAdmins
                                                    select admin.Admin).ToListAsync();
                List <PuzzleUser> allAuthors = await(from author in _context.EventAuthors
                                                     select author.Author).ToListAsync();

                foreach (PuzzleUser admin in allAdmins)
                {
                    admin.MayBeAdminOrAuthor = true;
                }
                foreach (PuzzleUser author in allAuthors)
                {
                    author.MayBeAdminOrAuthor = true;
                }

                await _context.SaveChangesAsync();

                transaction.Commit();
            }

            Status = "Admins and authors migrated";

            return(Page());
        }
Exemple #2
0
        public async Task <IActionResult> OnPostAsync()
        {
            if (!ModelState.IsValid)
            {
                return(Page());
            }

            var user = await _userManager.GetUserAsync(User);

            if (user == null)
            {
                return(NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."));
            }

            var thisPuzzleUser = await PuzzleUser.GetPuzzleUserForCurrentUser(_context, User, _userManager);

            // enforce access rights, do not let these change!
            Input.ID             = thisPuzzleUser.ID;
            Input.IsGlobalAdmin  = thisPuzzleUser.IsGlobalAdmin;
            Input.IdentityUserId = thisPuzzleUser.IdentityUserId;

            _context.Entry(thisPuzzleUser).State = EntityState.Detached;
            _context.Attach(Input).State         = EntityState.Modified;

            await _context.SaveChangesAsync(true);

            await _signInManager.RefreshSignInAsync(user);

            StatusMessage = "Your profile has been updated";
            return(RedirectToPage());
        }
        public async Task <IActionResult> OnPostAsync(string returnUrl = null)
        {
            if (string.IsNullOrEmpty(PuzzleUser.Email))
            {
                ModelState.AddModelError("PuzzleUser.Email", "An email is required.");
            }
            else if (!MailHelper.IsValidEmail(PuzzleUser.Email))
            {
                ModelState.AddModelError("PuzzleUser.Email", "This email address is not valid.");
            }

            if (!ModelState.IsValid)
            {
                return(Page());
            }

            var thisPuzzleUser = await PuzzleUser.GetPuzzleUserForCurrentUser(_context, User, _userManager);

            if (thisPuzzleUser != null)
            {
                _context.Entry(thisPuzzleUser).State = EntityState.Detached;
            }

            _context.Attach(PuzzleUser).State = EntityState.Modified;

            await _context.SaveChangesAsync();

            return(Redirect(returnUrl));
        }
        /// <summary>
        /// Helper for deleting teams that correctly deletes dependent objects
        /// </summary>
        /// <param name="team">Team to delete</param>
        public static async Task DeleteTeamAsync(PuzzleServerContext context, Team team)
        {
            var puzzleStates = from puzzleState in context.PuzzleStatePerTeam
                               where puzzleState.TeamID == team.ID
                               select puzzleState;

            context.PuzzleStatePerTeam.RemoveRange(puzzleStates);

            var hintStates = from hintState in context.HintStatePerTeam
                             where hintState.TeamID == team.ID
                             select hintState;

            context.HintStatePerTeam.RemoveRange(hintStates);

            var submissions = from submission in context.Submissions
                              where submission.Team == team
                              select submission;

            context.Submissions.RemoveRange(submissions);

            var annotations = from annotation in context.Annotations
                              where annotation.Team == team
                              select annotation;

            context.Annotations.RemoveRange(annotations);

            context.Teams.Remove(team);

            await context.SaveChangesAsync();
        }
        public static async Task UpdateTeamsWhoSentResponse(PuzzleServerContext context, Response response)
        {
            using (IDbContextTransaction transaction = context.Database.BeginTransaction(System.Data.IsolationLevel.Serializable))
            {
                var submissionsThatMatchResponse = await(from PuzzleStatePerTeam pspt in context.PuzzleStatePerTeam
                                                         join Submission sub in context.Submissions on pspt.Team equals sub.Team
                                                         where pspt.PuzzleID == response.PuzzleID &&
                                                         sub.SubmissionText == response.SubmittedText
                                                         select new { State = pspt, Submission = sub }).ToListAsync();

                if (submissionsThatMatchResponse.Count > 0)
                {
                    Puzzle puzzle = await context.Puzzles.Where((p) => p.ID == response.PuzzleID).FirstOrDefaultAsync();

                    foreach (var s in submissionsThatMatchResponse)
                    {
                        s.Submission.Response = response;
                        context.Attach(s.Submission).State = EntityState.Modified;

                        if (response.IsSolution && s.State.SolvedTime == null)
                        {
                            await SetSolveStateAsync(context, puzzle.Event, puzzle, s.State.Team, s.Submission.TimeSubmitted);
                        }
                    }

                    await context.SaveChangesAsync();

                    transaction.Commit();
                }
            }
        }
Exemple #6
0
        public async Task <IActionResult> OnPostAsync()
        {
            if (!ModelState.IsValid)
            {
                return(Page());
            }

            _context.Events.Add(Event);

            var loggedInUser = await PuzzleUser.GetPuzzleUserForCurrentUser(_context, User, _userManager);

            if (loggedInUser != null)
            {
                _context.EventAdmins.Add(new EventAdmins()
                {
                    Event = Event, Admin = loggedInUser
                });
                _context.EventAuthors.Add(new EventAuthors()
                {
                    Event = Event, Author = loggedInUser
                });
            }

            await _context.SaveChangesAsync();

            return(RedirectToPage("./Index"));
        }
        public async Task <IActionResult> OnPostConfirmationAsync(string returnUrl = null)
        {
            returnUrl = returnUrl ?? Url.Content("~/");
            // Get the information about the user from the external login provider
            var info = await _signInManager.GetExternalLoginInfoAsync();

            if (info == null)
            {
                ErrorMessage = "Error loading external login information during confirmation.";
                return(RedirectToPage("./Login", new { ReturnUrl = returnUrl }));
            }

            using (var transaction = _context.Database.BeginTransaction())
            {
                var user = new IdentityUser {
                    UserName = Input.Email, Email = Input.Email
                };
                var result = await _userManager.CreateAsync(user);

                if (result.Succeeded)
                {
                    result = await _userManager.AddLoginAsync(user, info);

                    if (result.Succeeded)
                    {
                        Input.IdentityUserId = user.Id;

                        if (ModelState.IsValid)
                        {
                            // If there are no GlobalAdmins make them the GlobalAdmin
                            if ((Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == Environments.Development) && !_context.PuzzleUsers.Where(u => u.IsGlobalAdmin).Any())
                            {
                                Input.IsGlobalAdmin = true;
                            }

                            _context.PuzzleUsers.Add(Input);
                            await _context.SaveChangesAsync(true);

                            transaction.Commit();

                            await _signInManager.SignInAsync(user, isPersistent : false);

                            _logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider);

                            return(LocalRedirect(returnUrl));
                        }
                    }

                    foreach (var error in result.Errors)
                    {
                        ModelState.AddModelError(string.Empty, error.Description);
                    }
                }
            }

            LoginProvider = info.LoginProvider;
            ReturnUrl     = returnUrl;
            return(Page());
        }
        /// <summary>
        /// Set the solve state of some puzzle state records. In the course of setting the state, instantiate any state records that are missing on the server.
        /// </summary>
        /// <param name="context">The puzzle DB context</param>
        /// <param name="eventObj">The event we are querying from</param>
        /// <param name="puzzle">
        ///     The puzzle; if null, get all puzzles in the event.
        /// </param>
        /// <param name="team">
        ///     The team; if null, get all the teams in the event.
        /// </param>
        /// <param name="value">The solve time (null if unsolving)</param>
        /// <param name="author"></param>
        /// <returns>
        ///     A task that can be awaited for the solve/unsolve operation
        /// </returns>
        public static async Task SetSolveStateAsync(
            PuzzleServerContext context,
            Event eventObj,
            Puzzle puzzle,
            Team team,
            DateTime?value,
            PuzzleUser author = null)
        {
            IQueryable <PuzzleStatePerTeam> statesQ = PuzzleStateHelper
                                                      .GetFullReadWriteQuery(context, eventObj, puzzle, team, author);

            List <PuzzleStatePerTeam> states = await statesQ.ToListAsync();

            for (int i = 0; i < states.Count; i++)
            {
                // Only allow solved time to be modified if it is being marked as unsolved (set to null) or if it is being solved for the first time
                if (value == null || states[i].SolvedTime == null)
                {
                    // Unlock puzzles when solving them
                    if (value != null && states[i].UnlockedTime == null)
                    {
                        states[i].UnlockedTime = value;
                    }

                    states[i].SolvedTime = value;
                }
            }

            // Award hint coins
            if (value != null && puzzle != null && puzzle.HintCoinsForSolve != 0)
            {
                if (team != null)
                {
                    team.HintCoinCount += puzzle.HintCoinsForSolve;
                }
                else
                {
                    var allTeams = from Team curTeam in context.Teams
                                   where curTeam.Event == eventObj
                                   select curTeam;
                    foreach (Team curTeam in allTeams)
                    {
                        curTeam.HintCoinCount += puzzle.HintCoinsForSolve;
                    }
                }
            }

            await context.SaveChangesAsync();

            // if this puzzle got solved, look for others to unlock
            if (puzzle != null && value != null)
            {
                await UnlockAnyPuzzlesThatThisSolveUnlockedAsync(context,
                                                                 eventObj,
                                                                 puzzle,
                                                                 team,
                                                                 value.Value);
            }
        }
 public static async Task SetTeamQualificationAsync(
     PuzzleServerContext context,
     Team team,
     bool value)
 {
     team.IsDisqualified = value;
     await context.SaveChangesAsync();
 }
        /// <summary>
        /// Adds a user to a team after performing a number of checks to make sure that the change is valid
        /// </summary>
        /// <param name="context">The context to update</param>
        /// <param name="Event">The event that the team is for</param>
        /// <param name="EventRole">The event role of the user that is making this change</param>
        /// <param name="teamId">The id of the team the player should be added to</param>
        /// <param name="userId">The user that should be added to the team</param>
        /// <returns>
        /// A tuple where the first element is a boolean that indicates whether the player was successfully
        /// added to the team and the second element is a message to display that explains the error in the
        /// case where the user was not successfully added to the team
        /// </returns>
        public static async Task <Tuple <bool, string> > AddMemberAsync(PuzzleServerContext context, Event Event, EventRole EventRole, int teamId, int userId)
        {
            Team team = await context.Teams.FirstOrDefaultAsync(m => m.ID == teamId);

            if (team == null)
            {
                return(new Tuple <bool, string>(false, $"Could not find team with ID '{teamId}'. Check to make sure the team hasn't been removed."));
            }

            var currentTeamMembers = await context.TeamMembers.Where(members => members.Team.ID == team.ID).ToListAsync();

            if (currentTeamMembers.Count >= Event.MaxTeamSize && EventRole != EventRole.admin)
            {
                return(new Tuple <bool, string>(false, $"The team '{team.Name}' is full."));
            }

            PuzzleUser user = await context.PuzzleUsers.FirstOrDefaultAsync(m => m.ID == userId);

            if (user == null)
            {
                return(new Tuple <bool, string>(false, $"Could not find user with ID '{userId}'. Check to make sure the user hasn't been removed."));
            }

            if (user.EmployeeAlias == null && currentTeamMembers.Where((m) => m.Member.EmployeeAlias == null).Count() >= Event.MaxExternalsPerTeam)
            {
                return(new Tuple <bool, string>(false, $"The team '{team.Name}' is already at its maximum count of non-employee players, and '{user.Email}' has no registered alias."));
            }

            if (await(from teamMember in context.TeamMembers
                      where teamMember.Member == user &&
                      teamMember.Team.Event == Event
                      select teamMember).AnyAsync())
            {
                return(new Tuple <bool, string>(false, $"'{user.Email}' is already on a team in this event."));
            }

            TeamMembers Member = new TeamMembers();

            Member.Team   = team;
            Member.Member = user;

            // Remove any applications the user might have started for this event
            var allApplications = from app in context.TeamApplications
                                  where app.Player == user &&
                                  app.Team.Event == Event
                                  select app;

            context.TeamApplications.RemoveRange(allApplications);

            context.TeamMembers.Add(Member);
            await context.SaveChangesAsync();

            return(new Tuple <bool, string>(true, ""));
        }
        /// <summary>
        /// Set the unlock state of some puzzle state records. In the course of setting the state, instantiate any state records that are missing on the server.
        /// </summary>
        /// <param name="context">The puzzle DB context</param>
        /// <param name="eventObj">The event we are querying from</param>
        /// <param name="puzzle">The puzzle; if null, get all puzzles in the event.</param>
        /// <param name="team">The team; if null, get all the teams in the event.</param>
        /// <param name="value">The unlock time (null if relocking)</param>
        /// <returns>A task that can be awaited for the unlock/lock operation</returns>
        public static async Task SetUnlockStateAsync(PuzzleServerContext context, Event eventObj, Puzzle puzzle, Team team, DateTime?value, PuzzleUser author = null)
        {
            IQueryable <PuzzleStatePerTeam> statesQ = await PuzzleStateHelper.GetFullReadWriteQueryAsync(context, eventObj, puzzle, team, author);

            List <PuzzleStatePerTeam> states = await statesQ.ToListAsync();

            for (int i = 0; i < states.Count; i++)
            {
                states[i].UnlockedTime = value;
            }
            await context.SaveChangesAsync();
        }
        /// <summary>
        /// Set the solve state of some puzzle state records. In the course of setting the state, instantiate any state records that are missing on the server.
        /// </summary>
        /// <param name="context">The puzzle DB context</param>
        /// <param name="eventObj">The event we are querying from</param>
        /// <param name="puzzle">
        ///     The puzzle; if null, get all puzzles in the event.
        /// </param>
        /// <param name="team">
        ///     The team; if null, get all the teams in the event.
        /// </param>
        /// <param name="value">The solve time (null if unsolving)</param>
        /// <param name="author"></param>
        /// <returns>
        ///     A task that can be awaited for the solve/unsolve operation
        /// </returns>
        public static async Task SetSolveStateAsync(
            PuzzleServerContext context,
            Event eventObj,
            Puzzle puzzle,
            Team team,
            DateTime?value,
            PuzzleUser author = null)
        {
            IQueryable <PuzzleStatePerTeam> statesQ = await PuzzleStateHelper
                                                      .GetFullReadWriteQueryAsync(context, eventObj, puzzle, team, author);

            List <PuzzleStatePerTeam> states = await statesQ.ToListAsync();

            for (int i = 0; i < states.Count; i++)
            {
                states[i].SolvedTime = value;
            }

            // Award hint coins
            if (value != null && puzzle != null && puzzle.HintCoinsForSolve != 0)
            {
                if (team != null)
                {
                    team.HintCoinCount += puzzle.HintCoinsForSolve;
                }
                else
                {
                    var allTeams = from Team curTeam in context.Teams
                                   where curTeam.Event == eventObj
                                   select curTeam;
                    foreach (Team curTeam in allTeams)
                    {
                        curTeam.HintCoinCount += puzzle.HintCoinsForSolve;
                    }
                }
            }

            await context.SaveChangesAsync();

            // if this puzzle got solved, look for others to unlock
            if (value != null)
            {
                await UnlockAnyPuzzlesThatThisSolveUnlockedAsync(context,
                                                                 eventObj,
                                                                 puzzle,
                                                                 team,
                                                                 value.Value);
            }
        }
        /// <summary>
        /// Set the unlock state of some puzzle state records. In the course of setting the state, instantiate any state records that are missing on the server.
        /// </summary>
        /// <param name="context">The puzzle DB context</param>
        /// <param name="eventObj">The event we are querying from</param>
        /// <param name="puzzle">The puzzle; if null, get all puzzles in the event.</param>
        /// <param name="team">The team; if null, get all the teams in the event.</param>
        /// <param name="value">The unlock time (null if relocking)</param>
        /// <returns>A task that can be awaited for the unlock/lock operation</returns>
        public static async Task SetUnlockStateAsync(PuzzleServerContext context, Event eventObj, Puzzle puzzle, Team team, DateTime?value, PuzzleUser author = null)
        {
            IQueryable <PuzzleStatePerTeam> statesQ = PuzzleStateHelper.GetFullReadWriteQuery(context, eventObj, puzzle, team, author);
            List <PuzzleStatePerTeam>       states  = await statesQ.ToListAsync();

            for (int i = 0; i < states.Count; i++)
            {
                // Only allow unlock time to be modified if we were relocking it (setting it to null) or unlocking it for the first time
                if (value == null || states[i].UnlockedTime == null)
                {
                    states[i].UnlockedTime = value;
                }
            }
            await context.SaveChangesAsync();
        }
        /// <summary>
        /// Helper for deleting puzzles that correctly deletes dependent objects
        /// </summary>
        /// <param name="puzzle">Puzzle to delete</param>
        public static async Task DeletePuzzleAsync(PuzzleServerContext context, Puzzle puzzle)
        {
            // Delete all files associated with this puzzle
            foreach (ContentFile content in puzzle.Contents)
            {
                await FileManager.DeleteBlobAsync(content.Url);
            }

            // Delete all Prerequisites where this puzzle depends on or is depended upon by another puzzle
            foreach (Prerequisites thisPrerequisite in context.Prerequisites.Where((r) => r.Puzzle == puzzle || r.Prerequisite == puzzle))
            {
                context.Prerequisites.Remove(thisPrerequisite);
            }

            context.Puzzles.Remove(puzzle);
            await context.SaveChangesAsync();
        }
        /// <summary>
        /// Helper for deleting teams that correctly deletes dependent objects
        /// </summary>
        /// <param name="team">Team to delete</param>
        public static async Task DeleteTeamAsync(PuzzleServerContext context, Team team, bool sendEmail = true)
        {
            var memberEmails = await(from teamMember in context.TeamMembers
                                     where teamMember.Team == team
                                     select teamMember.Member.Email).ToListAsync();
            var applicationEmails = await(from app in context.TeamApplications
                                          where app.Team == team
                                          select app.Player.Email).ToListAsync();

            var puzzleStates = from puzzleState in context.PuzzleStatePerTeam
                               where puzzleState.TeamID == team.ID
                               select puzzleState;

            context.PuzzleStatePerTeam.RemoveRange(puzzleStates);

            var hintStates = from hintState in context.HintStatePerTeam
                             where hintState.TeamID == team.ID
                             select hintState;

            context.HintStatePerTeam.RemoveRange(hintStates);

            var submissions = from submission in context.Submissions
                              where submission.Team == team
                              select submission;

            context.Submissions.RemoveRange(submissions);

            var annotations = from annotation in context.Annotations
                              where annotation.Team == team
                              select annotation;

            context.Annotations.RemoveRange(annotations);

            context.Teams.Remove(team);

            await context.SaveChangesAsync();

            if (sendEmail)
            {
                MailHelper.Singleton.SendPlaintextBcc(memberEmails.Union(applicationEmails),
                                                      $"{team.Event.Name}: Team '{team.Name}' has been deleted",
                                                      $"Sorry! You can apply to another team if you wish, or create your own.");
            }
        }
        public async Task <IActionResult> OnPostAsync(string returnUrl = null)
        {
            if (!ModelState.IsValid)
            {
                return(Page());
            }

            var thisPuzzleUser = await PuzzleUser.GetPuzzleUserForCurrentUser(_context, User, _userManager);

            if (thisPuzzleUser != null)
            {
                _context.Entry(thisPuzzleUser).State = EntityState.Detached;
            }

            _context.Attach(PuzzleUser).State = EntityState.Modified;

            await _context.SaveChangesAsync();

            return(Redirect(returnUrl));
        }
        public static async Task UpdateTeamsWhoSentResponse(PuzzleServerContext context, Response response)
        {
            using (IDbContextTransaction transaction = context.Database.BeginTransaction(System.Data.IsolationLevel.Serializable))
            {
                var submissionsThatMatchResponse = await(from PuzzleStatePerTeam pspt in context.PuzzleStatePerTeam
                                                         join Submission sub in context.Submissions on pspt.Team equals sub.Team
                                                         where pspt.PuzzleID == response.PuzzleID &&
                                                         sub.PuzzleID == response.PuzzleID &&
                                                         sub.SubmissionText == response.SubmittedText
                                                         select new { State = pspt, Submission = sub }).ToListAsync();

                if (submissionsThatMatchResponse.Count > 0)
                {
                    Puzzle puzzle = await context.Puzzles.Where((p) => p.ID == response.PuzzleID).FirstOrDefaultAsync();

                    foreach (var s in submissionsThatMatchResponse)
                    {
                        s.Submission.Response = response;
                        context.Attach(s.Submission).State = EntityState.Modified;

                        if (response.IsSolution && s.State.SolvedTime == null)
                        {
                            await SetSolveStateAsync(context, puzzle.Event, puzzle, s.State.Team, s.Submission.TimeSubmitted);
                        }
                    }

                    await context.SaveChangesAsync();

                    transaction.Commit();

                    var teamMembers = await(from TeamMembers tm in context.TeamMembers
                                            join Submission sub in context.Submissions on tm.Team equals sub.Team
                                            where sub.PuzzleID == response.PuzzleID && sub.SubmissionText == response.SubmittedText
                                            select tm.Member.Email).ToListAsync();
                    MailHelper.Singleton.SendPlaintextBcc(teamMembers,
                                                          $"{puzzle.Event.Name}: {response.Puzzle.Name} Response updated for '{response.SubmittedText}'",
                                                          $"The new response for this submission is: '{response.ResponseText}'.");
                }
            }
        }
        /// <summary>
        ///   This routine takes a single annotation that the requester has uploaded, and inserts
        ///   it if it doesn't exist and updates it otherwise.
        /// </summary>
        private async Task InsertOrUpdateOneAnnotation(SyncResponse response, int puzzleId, int teamId, int key, string contents)
        {
            // Check to see if the annotation already exists.  If so, update it and we're done.

            Annotation existingAnnotation =
                await context.Annotations.FirstOrDefaultAsync(a => a.PuzzleID == puzzleId && a.TeamID == teamId && a.Key == key);

            if (existingAnnotation != null)
            {
                context.Entry(existingAnnotation).State = EntityState.Detached;
                await UpdateOneAnnotation(response, puzzleId, teamId, key, contents);
            }
            else
            {
                // The annotation doesn't exist yet.  So, try to generate a new annotation, with version 1.

                Annotation annotation = new Annotation {
                    PuzzleID = puzzleId, TeamID = teamId, Key = key,
                    Version  = 1, Contents = contents, Timestamp = DateTime.Now
                };
                try {
                    context.Annotations.Add(annotation);
                    await context.SaveChangesAsync();
                }
                catch (DbUpdateException) {
                    // If the insert fails, there must already be an annotation there with the
                    // same puzzle ID, team ID, and key.  (This means there was a race condition:
                    // between the time we checked for the existence of a matching annotation and
                    // now, there was another insert.)  So, we need to update the existing one.

                    // But first, we need to detach the annotation from the context so the context
                    // doesn't think the annotation is in the database.

                    context.Entry(annotation).State = EntityState.Detached;
                    await UpdateOneAnnotation(response, puzzleId, teamId, key, contents);
                }
            }
        }
        /// <summary>
        /// Set the email only mode of some puzzle state records. In the course
        /// of setting the state, instantiate any state records that are
        /// missing on the server.
        /// </summary>
        /// <param name="context">The puzzle DB context</param>
        /// <param name="eventObj">The event we are querying from</param>
        /// <param name="puzzle">
        ///     The puzzle; if null, get all puzzles in the event.
        /// </param>
        /// <param name="team">
        ///     The team; if null, get all the teams in the event.
        /// </param>
        /// <param name="value">The new email only state for the puzzle</param>
        /// <param name="author"></param>
        /// <returns>
        ///     A task that can be awaited for the lockout operation
        /// </returns>
        public static async Task SetEmailOnlyModeAsync(
            PuzzleServerContext context,
            Event eventObj,
            Puzzle puzzle,
            Team team,
            bool value,
            PuzzleUser author = null)
        {
            IQueryable <PuzzleStatePerTeam> statesQ = await PuzzleStateHelper
                                                      .GetFullReadWriteQueryAsync(context, eventObj, puzzle, team, author);

            List <PuzzleStatePerTeam> states = await statesQ.ToListAsync();

            for (int i = 0; i < states.Count; i++)
            {
                states[i].IsEmailOnlyMode = value;
                if (value == true)
                {
                    states[i].WrongSubmissionCountBuffer += 50;
                }
            }

            await context.SaveChangesAsync();
        }
        /// <summary>
        /// Adds a user to a team after performing a number of checks to make sure that the change is valid
        /// </summary>
        /// <param name="context">The context to update</param>
        /// <param name="Event">The event that the team is for</param>
        /// <param name="EventRole">The event role of the user that is making this change</param>
        /// <param name="teamId">The id of the team the player should be added to</param>
        /// <param name="userId">The user that should be added to the team</param>
        /// <returns>
        /// A tuple where the first element is a boolean that indicates whether the player was successfully
        /// added to the team and the second element is a message to display that explains the error in the
        /// case where the user was not successfully added to the team
        /// </returns>
        public static async Task <Tuple <bool, string> > AddMemberAsync(PuzzleServerContext context, Event Event, EventRole EventRole, int teamId, int userId)
        {
            Team team = await context.Teams.FirstOrDefaultAsync(m => m.ID == teamId);

            if (team == null)
            {
                return(new Tuple <bool, string>(false, $"Could not find team with ID '{teamId}'. Check to make sure the team hasn't been removed."));
            }

            var currentTeamMembers = await context.TeamMembers.Where(members => members.Team.ID == team.ID).ToListAsync();

            if (currentTeamMembers.Count >= Event.MaxTeamSize && EventRole != EventRole.admin)
            {
                return(new Tuple <bool, string>(false, $"The team '{team.Name}' is full."));
            }

            PuzzleUser user = await context.PuzzleUsers.FirstOrDefaultAsync(m => m.ID == userId);

            if (user == null)
            {
                return(new Tuple <bool, string>(false, $"Could not find user with ID '{userId}'. Check to make sure the user hasn't been removed."));
            }

            if (user.EmployeeAlias == null && currentTeamMembers.Where((m) => m.Member.EmployeeAlias == null).Count() >= Event.MaxExternalsPerTeam)
            {
                return(new Tuple <bool, string>(false, $"The team '{team.Name}' is already at its maximum count of non-employee players, and '{user.Email}' has no registered alias."));
            }

            if (await(from teamMember in context.TeamMembers
                      where teamMember.Member == user &&
                      teamMember.Team.Event == Event
                      select teamMember).AnyAsync())
            {
                return(new Tuple <bool, string>(false, $"'{user.Email}' is already on a team in this event."));
            }

            TeamMembers Member = new TeamMembers();

            Member.Team   = team;
            Member.Member = user;

            // Remove any applications the user might have started for this event
            var allApplications = from app in context.TeamApplications
                                  where app.Player == user &&
                                  app.Team.Event == Event
                                  select app;

            context.TeamApplications.RemoveRange(allApplications);

            context.TeamMembers.Add(Member);
            await context.SaveChangesAsync();

            MailHelper.Singleton.SendPlaintextWithoutBcc(new string[] { team.PrimaryContactEmail, user.Email },
                                                         $"{Event.Name}: {user.Name} has now joined {team.Name}!",
                                                         $"Have a great time!");

            var teamCount = await context.TeamMembers.Where(members => members.Team.ID == team.ID).CountAsync();

            if (teamCount >= Event.MaxTeamSize)
            {
                var extraApplications = await(from app in context.TeamApplications
                                              where app.Team == team
                                              select app).ToListAsync();
                context.TeamApplications.RemoveRange(extraApplications);

                var extraApplicationMails = from app in extraApplications
                                            select app.Player.Email;

                MailHelper.Singleton.SendPlaintextBcc(extraApplicationMails.ToList(),
                                                      $"{Event.Name}: You can no longer join {team.Name} because it is now full",
                                                      $"Sorry! You can apply to another team if you wish.");
            }

            return(new Tuple <bool, string>(true, ""));
        }
        /// <summary>
        /// Unlock any puzzles that need to be unlocked due to the recent solve of a prerequisite.
        /// </summary>
        /// <param name="context">The puzzle DB context</param>
        /// <param name="eventObj">The event we are working in</param>
        /// <param name="puzzleJustSolved">The puzzle just solved</param>
        /// <param name="team">The team that just solved; if null, all the teams in the event.</param>
        /// <param name="unlockTime">The time that the puzzle should be marked as unlocked.</param>
        /// <returns></returns>
        private static async Task UnlockAnyPuzzlesThatThisSolveUnlockedAsync(PuzzleServerContext context, Event eventObj, Puzzle puzzleJustSolved, Team team, DateTime unlockTime)
        {
            // a simple query for all puzzle IDs in the event - will be used at least once below
            IQueryable <int> allPuzzleIDsQ = context.Puzzles.Where(p => p.Event == eventObj).Select(p => p.ID);


            // get the prerequisites for all puzzles that need an update
            // information we get per puzzle: { id, min count, number of solved prereqs including this one }
            var prerequisiteDataForNeedsUpdatePuzzles = (from possibleUnlock in context.Prerequisites
                                                         join unlockedBy in context.Prerequisites on possibleUnlock.PuzzleID equals unlockedBy.PuzzleID
                                                         join pspt in context.PuzzleStatePerTeam on unlockedBy.PrerequisiteID equals pspt.PuzzleID
                                                         join puz in context.Puzzles on unlockedBy.PrerequisiteID equals puz.ID
                                                         where possibleUnlock.Prerequisite == puzzleJustSolved && (team == null || pspt.TeamID == team.ID) && pspt.SolvedTime != null
                                                         group puz by new { unlockedBy.PuzzleID, unlockedBy.Puzzle.MinPrerequisiteCount, pspt.TeamID } into g
                                                         select new
            {
                PuzzleID = g.Key.PuzzleID,
                TeamID = g.Key.TeamID,
                g.Key.MinPrerequisiteCount,
                TotalPrerequisiteCount = g.Sum(p => (p.PrerequisiteWeight ?? 1))
            }).ToList();

            // Are we updating one team or all teams?
            List <Team> teamsToUpdate = team == null ? await context.Teams.Where(t => t.Event == eventObj).ToListAsync() : new List <Team>()
            {
                team
            };

            // Update teams one at a time
            foreach (Team t in teamsToUpdate)
            {
                // Collect the IDs of all solved/unlocked puzzles for this team
                // sparse lookup is fine since if the state is missing it isn't unlocked or solved!
                var puzzleStateForTeamT = await PuzzleStateHelper.GetSparseQuery(context, eventObj, null, t)
                                          .Select(state => new { state.PuzzleID, state.UnlockedTime, state.SolvedTime })
                                          .ToListAsync();

                // Make a hash set out of them for easy lookup in case we have several prerequisites to chase
                HashSet <int> unlockedPuzzleIDsForTeamT = new HashSet <int>();
                HashSet <int> solvedPuzzleIDsForTeamT   = new HashSet <int>();

                foreach (var puzzleState in puzzleStateForTeamT)
                {
                    if (puzzleState.UnlockedTime != null)
                    {
                        unlockedPuzzleIDsForTeamT.Add(puzzleState.PuzzleID);
                    }

                    if (puzzleState.SolvedTime != null)
                    {
                        solvedPuzzleIDsForTeamT.Add(puzzleState.PuzzleID);
                    }
                }

                // now loop through all puzzles and count up who needs to be unlocked
                foreach (var puzzleToUpdate in prerequisiteDataForNeedsUpdatePuzzles)
                {
                    // already unlocked? skip
                    if (unlockedPuzzleIDsForTeamT.Contains(puzzleToUpdate.PuzzleID))
                    {
                        continue;
                    }

                    // Enough puzzles unlocked by count? Let's unlock it
                    if (puzzleToUpdate.TeamID == t.ID && puzzleToUpdate.TotalPrerequisiteCount >= puzzleToUpdate.MinPrerequisiteCount)
                    {
                        PuzzleStatePerTeam state = await context.PuzzleStatePerTeam.Where(s => s.PuzzleID == puzzleToUpdate.PuzzleID && s.Team == t).FirstAsync();

                        state.UnlockedTime = unlockTime;
                    }
                }
            }

            // after looping through all teams, send one update with all changes made
            await context.SaveChangesAsync();
        }
Exemple #22
0
        /// <summary>
        /// Get a writable query of puzzle state. In the course of constructing the query, it will instantiate any state records that are missing on the server.
        /// </summary>
        /// <param name="context">The puzzle DB context</param>
        /// <param name="eventObj">The event we are querying from</param>
        /// <param name="puzzle">The puzzle; if null, get all puzzles in the event.</param>
        /// <param name="team">The team; if null, get all the teams in the event.</param>
        /// <returns>A query of PuzzleStatePerTeam objects that can be sorted and instantiated, but you can't edit the results.</returns>
        public static async Task <IQueryable <PuzzleStatePerTeam> > GetFullReadWriteQueryAsync(PuzzleServerContext context, Event eventObj, Puzzle puzzle, Team team)
        {
            if (context == null)
            {
                throw new ArgumentNullException("Context required.");
            }

            if (eventObj == null)
            {
                throw new ArgumentNullException("Event required.");
            }

            if (puzzle != null && team != null)
            {
                PuzzleStatePerTeam state = await context.PuzzleStatePerTeam.Where(s => s.Puzzle == puzzle && s.Team == team).FirstOrDefaultAsync();

                if (state == null)
                {
                    context.PuzzleStatePerTeam.Add(new DataModel.PuzzleStatePerTeam()
                    {
                        Puzzle = puzzle, Team = team
                    });
                }
            }
            else if (puzzle != null)
            {
                var teamIdsQ            = context.Teams.Where(p => p.Event == eventObj).Select(p => p.ID);
                var puzzleStateTeamIdsQ = context.PuzzleStatePerTeam.Where(s => s.Puzzle == puzzle).Select(s => s.TeamID);
                var teamIdsWithoutState = await teamIdsQ.Except(puzzleStateTeamIdsQ).ToListAsync();

                if (teamIdsWithoutState.Count > 0)
                {
                    for (int i = 0; i < teamIdsWithoutState.Count; i++)
                    {
                        context.PuzzleStatePerTeam.Add(new DataModel.PuzzleStatePerTeam()
                        {
                            Puzzle = puzzle, TeamID = teamIdsWithoutState[i]
                        });
                    }
                }
            }
            else if (team != null)
            {
                var puzzleIdsQ            = context.Puzzles.Where(p => p.Event == eventObj).Select(p => p.ID);
                var puzzleStatePuzzleIdsQ = context.PuzzleStatePerTeam.Where(s => s.Team == team).Select(s => s.PuzzleID);
                var puzzleIdsWithoutState = await puzzleIdsQ.Except(puzzleStatePuzzleIdsQ).ToListAsync();

                if (puzzleIdsWithoutState.Count > 0)
                {
                    for (int i = 0; i < puzzleIdsWithoutState.Count; i++)
                    {
                        context.PuzzleStatePerTeam.Add(new DataModel.PuzzleStatePerTeam()
                        {
                            Team = team, PuzzleID = puzzleIdsWithoutState[i]
                        });
                    }
                }
            }
            else if (puzzle == null && team == null)
            {
                throw new NotImplementedException("Full event query is NYI and may never be needed");
            }

            await context.SaveChangesAsync(); // query below will not return these unless we save

            // now this query is no longer sparse because we just filled it all out!
            return(GetSparseQuery(context, eventObj, puzzle, team));
        }
Exemple #23
0
        public async Task <IActionResult> OnPostAsync()
        {
            if (!ModelState.IsValid)
            {
                return(Page());
            }

            using (var transaction = _context.Database.BeginTransaction())
            {
                //
                // Add the event and save, so the event gets an ID.
                //
                Event.TeamRegistrationBegin       = DateTime.UtcNow;
                Event.TeamRegistrationEnd         = Event.AnswerSubmissionEnd;
                Event.TeamNameChangeEnd           = Event.AnswerSubmissionEnd;
                Event.TeamMembershipChangeEnd     = Event.AnswerSubmissionEnd;
                Event.TeamMiscDataChangeEnd       = Event.AnswerSubmissionEnd;
                Event.TeamDeleteEnd               = Event.AnswerSubmissionEnd;
                Event.AnswersAvailableBegin       = Event.AnswerSubmissionEnd;
                Event.StandingsAvailableBegin     = DateTime.UtcNow;
                Event.LockoutIncorrectGuessLimit  = 5;
                Event.LockoutIncorrectGuessPeriod = 1;
                Event.LockoutDurationMultiplier   = 2;
                Event.MaxSubmissionCount          = 50;
                Event.MaxNumberOfTeams            = 120;
                Event.MaxExternalsPerTeam         = 9;
                Event.MaxTeamSize = 12;
                _context.Events.Add(Event);

                await _context.SaveChangesAsync();

                //
                // Add start puzzle, three module puzzles, and one module meta (marked as the final event puzzle for this demo)
                //
                Puzzle start = new Puzzle
                {
                    Name     = "!!!Get Hopping!!!",
                    Event    = Event,
                    IsPuzzle = false,
                    IsGloballyVisiblePrerequisite = true,
                    Description = "Start the event",
                };
                _context.Puzzles.Add(start);

                Puzzle easy = new Puzzle
                {
                    Name                 = "Bunny Slope",
                    Event                = Event,
                    IsPuzzle             = true,
                    SolveValue           = 10,
                    HintCoinsForSolve    = 1,
                    Group                = "Thumper's Stumpers",
                    OrderInGroup         = 1,
                    MinPrerequisiteCount = 1,
                    Description          = "Bunsweeper",
                };
                _context.Puzzles.Add(easy);

                Puzzle intermediate = new Puzzle
                {
                    Name                        = "Rabbit Run (automatically solves in ~3 mins)",
                    Event                       = Event,
                    IsPuzzle                    = true,
                    SolveValue                  = 10,
                    HintCoinsForSolve           = 2,
                    Group                       = "Thumper's Stumpers",
                    OrderInGroup                = 2,
                    MinPrerequisiteCount        = 1,
                    MinutesToAutomaticallySolve = 3,
                    Description                 = "Rabbit's Cube",
                };
                _context.Puzzles.Add(intermediate);

                Puzzle hard = new Puzzle
                {
                    Name                 = "Hare-Raising",
                    Event                = Event,
                    IsPuzzle             = true,
                    SolveValue           = 10,
                    HintCoinsForSolve    = 3,
                    Group                = "Thumper's Stumpers",
                    OrderInGroup         = 3,
                    MinPrerequisiteCount = 1,
                    Description          = "Lateral Leaping",
                };
                _context.Puzzles.Add(hard);

                Puzzle meta = new Puzzle
                {
                    Name                 = "Lagomorph Meta",
                    Event                = Event,
                    IsPuzzle             = true,
                    IsMetaPuzzle         = true,
                    IsFinalPuzzle        = true,
                    SolveValue           = 100,
                    Group                = "Thumper's Stumpers",
                    OrderInGroup         = 99,
                    MinPrerequisiteCount = 2,
                    Description          = "Word Hutch",
                };
                _context.Puzzles.Add(meta);

                Puzzle other = new Puzzle
                {
                    Name                 = "Rabbit Season",
                    Event                = Event,
                    IsPuzzle             = true,
                    SolveValue           = 10,
                    Group                = "Daffy's Delights",
                    OrderInGroup         = 1,
                    MinPrerequisiteCount = 1,
                    Description          = "Hip Hop Identification",
                    CustomURL            = "https://www.bing.com/images/search?q=%22rabbit%22",
                };
                _context.Puzzles.Add(other);

                Puzzle cheat = new Puzzle
                {
                    Name                 = "You're Despicable (cheat code)",
                    Event                = Event,
                    IsPuzzle             = true,
                    IsCheatCode          = true,
                    SolveValue           = -1,
                    Group                = "Daffy's Delights",
                    OrderInGroup         = 2,
                    MinPrerequisiteCount = 1,
                    Description          = "Duck Konundrum",
                };
                _context.Puzzles.Add(cheat);

                Puzzle lockIntro = new Puzzle
                {
                    Name                 = "Wouldn't you know... (whistle stop intro)",
                    Event                = Event,
                    IsPuzzle             = true,
                    SolveValue           = 0,
                    Group                = "Roger's Railway",
                    OrderInGroup         = 1,
                    MinPrerequisiteCount = 1,
                    Description          = "Whistle Hop Intro",
                };
                _context.Puzzles.Add(lockIntro);

                Puzzle lockPuzzle = new Puzzle
                {
                    Name                  = "...Locked! (whistle stop, lasts 5 minutes)",
                    Event                 = Event,
                    IsPuzzle              = true,
                    SolveValue            = 0,
                    Group                 = "Roger's Railway",
                    OrderInGroup          = 2,
                    MinPrerequisiteCount  = 1,
                    MinutesOfEventLockout = 5,
                    Description           = "Whistle Hop",
                };
                _context.Puzzles.Add(lockPuzzle);

                Puzzle kitchenSyncPuzzle = new Puzzle
                {
                    Name                 = "Kitchen Sync",
                    Event                = Event,
                    IsPuzzle             = true,
                    SolveValue           = 10,
                    Group                = "Sync Test",
                    OrderInGroup         = 1,
                    MinPrerequisiteCount = 1
                };
                _context.Puzzles.Add(kitchenSyncPuzzle);

                Puzzle heatSyncPuzzle = new Puzzle
                {
                    Name                 = "Heat Sync",
                    Event                = Event,
                    IsPuzzle             = true,
                    SolveValue           = 10,
                    Group                = "Sync Test",
                    OrderInGroup         = 2,
                    MinPrerequisiteCount = 1
                };
                _context.Puzzles.Add(heatSyncPuzzle);

                Puzzle lipSyncPuzzle = new Puzzle
                {
                    Name                 = "Lip Sync",
                    Event                = Event,
                    IsPuzzle             = true,
                    SolveValue           = 10,
                    Group                = "Sync Test",
                    OrderInGroup         = 3,
                    MinPrerequisiteCount = 1
                };
                _context.Puzzles.Add(lipSyncPuzzle);

                Puzzle syncTestMetapuzzle = new Puzzle
                {
                    Name                 = "Sync Test",
                    Event                = Event,
                    IsPuzzle             = true,
                    IsMetaPuzzle         = true,
                    SolveValue           = 50,
                    Group                = "Sync Test",
                    OrderInGroup         = 99,
                    MinPrerequisiteCount = 1,
                    MaxAnnotationKey     = 400
                };
                _context.Puzzles.Add(syncTestMetapuzzle);

                await _context.SaveChangesAsync();

                //
                // Add responses, PARTIAL is a partial, ANSWER is the answer.
                //
                _context.Responses.Add(new Response()
                {
                    Puzzle = easy, SubmittedText = "PARTIAL", ResponseText = "Keep going..."
                });
                _context.Responses.Add(new Response()
                {
                    Puzzle = easy, SubmittedText = "ANSWER", ResponseText = "Correct!", IsSolution = true
                });
                _context.Responses.Add(new Response()
                {
                    Puzzle = intermediate, SubmittedText = "PARTIAL", ResponseText = "Keep going..."
                });
                _context.Responses.Add(new Response()
                {
                    Puzzle = intermediate, SubmittedText = "ANSWER", ResponseText = "Correct!", IsSolution = true
                });
                _context.Responses.Add(new Response()
                {
                    Puzzle = hard, SubmittedText = "PARTIAL", ResponseText = "Keep going..."
                });
                _context.Responses.Add(new Response()
                {
                    Puzzle = hard, SubmittedText = "ANSWER", ResponseText = "Correct!", IsSolution = true
                });
                _context.Responses.Add(new Response()
                {
                    Puzzle = meta, SubmittedText = "PARTIAL", ResponseText = "Keep going..."
                });
                _context.Responses.Add(new Response()
                {
                    Puzzle = meta, SubmittedText = "ANSWER", ResponseText = "Correct!", IsSolution = true
                });
                _context.Responses.Add(new Response()
                {
                    Puzzle = other, SubmittedText = "PARTIAL", ResponseText = "Keep going..."
                });
                _context.Responses.Add(new Response()
                {
                    Puzzle = other, SubmittedText = "ANSWER", ResponseText = "Correct!", IsSolution = true
                });
                _context.Responses.Add(new Response()
                {
                    Puzzle = cheat, SubmittedText = "PARTIAL", ResponseText = "Keep going..."
                });
                _context.Responses.Add(new Response()
                {
                    Puzzle = cheat, SubmittedText = "ANSWER", ResponseText = "Correct!", IsSolution = true
                });
                _context.Responses.Add(new Response()
                {
                    Puzzle = lockIntro, SubmittedText = "PARTIAL", ResponseText = "Keep going..."
                });
                _context.Responses.Add(new Response()
                {
                    Puzzle = lockIntro, SubmittedText = "ANSWER", ResponseText = "Correct!", IsSolution = true
                });
                _context.Responses.Add(new Response()
                {
                    Puzzle = lockPuzzle, SubmittedText = "PARTIAL", ResponseText = "Keep going..."
                });
                _context.Responses.Add(new Response()
                {
                    Puzzle = lockPuzzle, SubmittedText = "ANSWER", ResponseText = "Correct!", IsSolution = true
                });
                _context.Responses.Add(new Response()
                {
                    Puzzle = kitchenSyncPuzzle, SubmittedText = "PARTIAL", ResponseText = "Keep going..."
                });
                _context.Responses.Add(new Response()
                {
                    Puzzle = kitchenSyncPuzzle, SubmittedText = "SYNC", ResponseText = "Correct!", IsSolution = true
                });
                _context.Responses.Add(new Response()
                {
                    Puzzle = heatSyncPuzzle, SubmittedText = "PARTIAL", ResponseText = "Keep going..."
                });
                _context.Responses.Add(new Response()
                {
                    Puzzle = heatSyncPuzzle, SubmittedText = "OR", ResponseText = "Correct!", IsSolution = true
                });
                _context.Responses.Add(new Response()
                {
                    Puzzle = lipSyncPuzzle, SubmittedText = "PARTIAL", ResponseText = "Keep going..."
                });
                _context.Responses.Add(new Response()
                {
                    Puzzle = lipSyncPuzzle, SubmittedText = "SWIM", ResponseText = "Correct!", IsSolution = true
                });
                _context.Responses.Add(new Response()
                {
                    Puzzle = syncTestMetapuzzle, SubmittedText = "PARTIAL", ResponseText = "Keep going..."
                });
                _context.Responses.Add(new Response()
                {
                    Puzzle = syncTestMetapuzzle, SubmittedText = "SYNCORSWIM", ResponseText = "Correct!", IsSolution = true
                });

                string hint1Description = "Tell me about the rabbits, George.";
                string hint1Content     = "O.K. Some day – we’re gonna get the jack together and we’re gonna have a little house and a couple of acres an’ a cow and some pigs and...";
                string hint2Description = "Go on... George. How I get to tend the rabbits.";
                string hint2Content     = "Well, we’ll have a big vegetable patch and a rabbit-hutch and chickens.";
                _context.Hints.Add(new Hint()
                {
                    Puzzle = easy, Description = hint1Description, DisplayOrder = 0, Cost = 0, Content = hint1Content
                });
                _context.Hints.Add(new Hint()
                {
                    Puzzle = easy, Description = hint2Description, DisplayOrder = 1, Cost = 1, Content = hint2Content
                });
                _context.Hints.Add(new Hint()
                {
                    Puzzle = intermediate, Description = hint1Description, DisplayOrder = 0, Cost = 0, Content = hint1Content
                });
                _context.Hints.Add(new Hint()
                {
                    Puzzle = intermediate, Description = hint2Description, DisplayOrder = 1, Cost = 1, Content = hint2Content
                });
                _context.Hints.Add(new Hint()
                {
                    Puzzle = hard, Description = hint1Description, DisplayOrder = 0, Cost = 0, Content = hint1Content
                });
                _context.Hints.Add(new Hint()
                {
                    Puzzle = hard, Description = hint2Description, DisplayOrder = 1, Cost = 1, Content = hint2Content
                });
                _context.Hints.Add(new Hint()
                {
                    Puzzle = meta, Description = hint1Description, DisplayOrder = 0, Cost = 0, Content = hint1Content
                });
                _context.Hints.Add(new Hint()
                {
                    Puzzle = meta, Description = hint2Description, DisplayOrder = 1, Cost = 1, Content = hint2Content
                });

                await _context.SaveChangesAsync();

                //
                // Set up prequisite links.
                // The first two depend on start puzzle, then the third depends on one of the first two, then the meta depends on two of the first three.
                //
                _context.Prerequisites.Add(new Prerequisites()
                {
                    Puzzle = easy, Prerequisite = start
                });
                _context.Prerequisites.Add(new Prerequisites()
                {
                    Puzzle = intermediate, Prerequisite = start
                });
                _context.Prerequisites.Add(new Prerequisites()
                {
                    Puzzle = hard, Prerequisite = easy
                });
                _context.Prerequisites.Add(new Prerequisites()
                {
                    Puzzle = hard, Prerequisite = intermediate
                });
                _context.Prerequisites.Add(new Prerequisites()
                {
                    Puzzle = meta, Prerequisite = easy
                });
                _context.Prerequisites.Add(new Prerequisites()
                {
                    Puzzle = meta, Prerequisite = intermediate
                });
                _context.Prerequisites.Add(new Prerequisites()
                {
                    Puzzle = meta, Prerequisite = hard
                });
                _context.Prerequisites.Add(new Prerequisites()
                {
                    Puzzle = other, Prerequisite = start
                });
                _context.Prerequisites.Add(new Prerequisites()
                {
                    Puzzle = cheat, Prerequisite = start
                });
                _context.Prerequisites.Add(new Prerequisites()
                {
                    Puzzle = lockIntro, Prerequisite = start
                });
                _context.Prerequisites.Add(new Prerequisites()
                {
                    Puzzle = lockPuzzle, Prerequisite = lockIntro
                });
                _context.Prerequisites.Add(new Prerequisites()
                {
                    Puzzle = kitchenSyncPuzzle, Prerequisite = start
                });
                _context.Prerequisites.Add(new Prerequisites()
                {
                    Puzzle = heatSyncPuzzle, Prerequisite = start
                });
                _context.Prerequisites.Add(new Prerequisites()
                {
                    Puzzle = lipSyncPuzzle, Prerequisite = start
                });
                _context.Prerequisites.Add(new Prerequisites()
                {
                    Puzzle = syncTestMetapuzzle, Prerequisite = start
                });

                await _context.SaveChangesAsync();

                //
                // Create puzzle pieces.
                //
                _context.Pieces.Add(MakePiece(syncTestMetapuzzle, 0, 1, "xxx xxxx'x x xXxxx!", 3, "What Crocodile Dundee might say"));
                _context.Pieces.Add(MakePiece(syncTestMetapuzzle, 1, 2, "xxx xxxx xxxx xxx Xxxxx", 1, "In <i>Hey Diddle Diddle</i>, what the dish did"));
                _context.Pieces.Add(MakePiece(syncTestMetapuzzle, 1, 3, "xxXx", 1, "It smells"));
                _context.Pieces.Add(MakePiece(syncTestMetapuzzle, 2, 4, "xxX", 4, "You reach things by extending it"));
                _context.Pieces.Add(MakePiece(syncTestMetapuzzle, 2, 5, "Xxx xxxxx", 1, "Result of the <i>Exxon Valdez</i> crash"));
                _context.Pieces.Add(MakePiece(syncTestMetapuzzle, 2, 6, "xxxxX", 2, "What you make out of turkey drippings"));
                _context.Pieces.Add(MakePiece(syncTestMetapuzzle, 3, 7, "xxxXx xxx", 4, "Another name for a slow cooker"));
                _context.Pieces.Add(MakePiece(syncTestMetapuzzle, 4, 8, "xXxxxx", 3, "The index is one of them"));
                _context.Pieces.Add(MakePiece(syncTestMetapuzzle, 4, 9, "xxxxX", 2, "It can suffer when you play tennis"));
                _context.Pieces.Add(MakePiece(syncTestMetapuzzle, 4, 10, "xxxxxxX xxxxxxx", 2, "What Chekov asked strangers the location of in Star Trek IV, garnering suspicion due to his Russian accent"));

                await _context.SaveChangesAsync();

                //
                // Create teams. Can we add players to these?
                //
                Team team1 = new Team {
                    Name = "Team Bugs", Event = Event
                };
                _context.Teams.Add(team1);

                Team team2 = new Team {
                    Name = "Team Babs", Event = Event
                };
                _context.Teams.Add(team2);

                Team team3 = new Team {
                    Name = "Team Buster", Event = Event
                };
                _context.Teams.Add(team3);

                Team teamLoneWolf = null;
                if (AddCreatorToLoneWolfTeam)
                {
                    teamLoneWolf = new Team {
                        Name = "Lone Wolf", Event = Event
                    };
                    _context.Teams.Add(teamLoneWolf);
                }

                var demoCreatorUser = await PuzzleUser.GetPuzzleUserForCurrentUser(_context, User, _userManager);

                if (demoCreatorUser != null)
                {
                    //
                    // Event admin/author
                    //
                    _context.EventAdmins.Add(new EventAdmins()
                    {
                        Event = Event, Admin = demoCreatorUser
                    });
                    _context.EventAuthors.Add(new EventAuthors()
                    {
                        Event = Event, Author = demoCreatorUser
                    });

                    //
                    // Puzzle author (for Thumper module only)
                    //
                    _context.PuzzleAuthors.Add(new PuzzleAuthors()
                    {
                        Puzzle = easy, Author = demoCreatorUser
                    });
                    _context.PuzzleAuthors.Add(new PuzzleAuthors()
                    {
                        Puzzle = intermediate, Author = demoCreatorUser
                    });
                    _context.PuzzleAuthors.Add(new PuzzleAuthors()
                    {
                        Puzzle = hard, Author = demoCreatorUser
                    });
                    _context.PuzzleAuthors.Add(new PuzzleAuthors()
                    {
                        Puzzle = meta, Author = demoCreatorUser
                    });
                }

                // TODO: Files (need to know how to detect whether local blob storage is configured)
                // Is there a point to adding Feedback or is that quick/easy enough to demo by hand?

                await _context.SaveChangesAsync();

                if (teamLoneWolf != null)
                {
                    _context.TeamMembers.Add(new TeamMembers()
                    {
                        Team = teamLoneWolf, Member = demoCreatorUser
                    });
                }

                // line up all hints
                var teams = await _context.Teams.Where((t) => t.Event == Event).ToListAsync();

                var hints = await _context.Hints.Where((h) => h.Puzzle.Event == Event).ToListAsync();

                var puzzles = await _context.Puzzles.Where(p => p.Event == Event).ToListAsync();

                foreach (Team team in teams)
                {
                    foreach (Hint hint in hints)
                    {
                        _context.HintStatePerTeam.Add(new HintStatePerTeam()
                        {
                            Hint = hint, Team = team
                        });
                    }
                    foreach (Puzzle puzzle in puzzles)
                    {
                        _context.PuzzleStatePerTeam.Add(new PuzzleStatePerTeam()
                        {
                            PuzzleID = puzzle.ID, TeamID = team.ID
                        });
                    }
                }

                await _context.SaveChangesAsync();

                //
                // Mark the start puzzle as solved if we were asked to.
                //
                if (StartTheEvent)
                {
                    await PuzzleStateHelper.SetSolveStateAsync(_context, Event, start, null, DateTime.UtcNow);
                }

                transaction.Commit();
            }

            return(RedirectToPage("./Index"));
        }
        /// <summary>
        /// Unlock any puzzles that need to be unlocked due to the recent solve of a prerequisite.
        /// </summary>
        /// <param name="context">The puzzle DB context</param>
        /// <param name="eventObj">The event we are working in</param>
        /// <param name="puzzleJustSolved">The puzzle just solved; if null, all the puzzles in the event (which will make more sense once we add per author filtering)</param>
        /// <param name="team">The team that just solved; if null, all the teams in the event.</param>
        /// <param name="unlockTime">The time that the puzzle should be marked as unlocked.</param>
        /// <returns></returns>
        private static async Task UnlockAnyPuzzlesThatThisSolveUnlockedAsync(PuzzleServerContext context, Event eventObj, Puzzle puzzleJustSolved, Team team, DateTime unlockTime)
        {
            // a simple query for all puzzle IDs in the event - will be used at least once below
            IQueryable <int> allPuzzleIDsQ = context.Puzzles.Where(p => p.Event == eventObj).Select(p => p.ID);

            // if we solved a group of puzzles, every puzzle needs an update.
            // if we solved a single puzzle, only update the puzzles that have that one as a prerequisite.
            IQueryable <int> needsUpdatePuzzleIDsQ =
                puzzleJustSolved == null ?
                allPuzzleIDsQ :
                context.Prerequisites.Where(pre => pre.Prerequisite == puzzleJustSolved).Select(pre => pre.PuzzleID).Distinct();

            // get the prerequisites for all puzzles that need an update
            // information we get per puzzle: { id, min count, prerequisite IDs }
            var prerequisiteDataForNeedsUpdatePuzzles = await context.Prerequisites
                                                        .Where(pre => needsUpdatePuzzleIDsQ.Contains(pre.PuzzleID))
                                                        .GroupBy(pre => pre.Puzzle)
                                                        .Select(g => new {
                PuzzleID = g.Key.ID,
                g.Key.MinPrerequisiteCount,
                PrerequisiteIDs = g.Select(pre => pre.PrerequisiteID)
            })
                                                        .ToListAsync();

            // Are we updating one team or all teams?
            List <Team> teamsToUpdate = team == null ? await context.Teams.Where(t => t.Event == eventObj).ToListAsync() : new List <Team>()
            {
                team
            };

            // Update teams one at a time
            foreach (Team t in teamsToUpdate)
            {
                // Collect the IDs of all solved/unlocked puzzles for this team
                // sparse lookup is fine since if the state is missing it isn't unlocked or solved!
                var puzzleStateForTeamT = await PuzzleStateHelper.GetSparseQuery(context, eventObj, null, t)
                                          .Select(state => new { state.PuzzleID, state.UnlockedTime, state.SolvedTime })
                                          .ToListAsync();

                // Make a hash set out of them for easy lookup in case we have several prerequisites to chase
                HashSet <int> unlockedPuzzleIDsForTeamT = new HashSet <int>();
                HashSet <int> solvedPuzzleIDsForTeamT   = new HashSet <int>();

                foreach (var puzzleState in puzzleStateForTeamT)
                {
                    if (puzzleState.UnlockedTime != null)
                    {
                        unlockedPuzzleIDsForTeamT.Add(puzzleState.PuzzleID);
                    }

                    if (puzzleState.SolvedTime != null)
                    {
                        solvedPuzzleIDsForTeamT.Add(puzzleState.PuzzleID);
                    }
                }

                // now loop through all puzzles and count up who needs to be unlocked
                foreach (var puzzleToUpdate in prerequisiteDataForNeedsUpdatePuzzles)
                {
                    // already unlocked? skip
                    if (unlockedPuzzleIDsForTeamT.Contains(puzzleToUpdate.PuzzleID))
                    {
                        continue;
                    }

                    // Enough puzzles unlocked by count? Let's unlock it
                    if (puzzleToUpdate.PrerequisiteIDs.Where(id => solvedPuzzleIDsForTeamT.Contains(id)).Count() >= puzzleToUpdate.MinPrerequisiteCount)
                    {
                        PuzzleStatePerTeam state = await context.PuzzleStatePerTeam.Where(s => s.PuzzleID == puzzleToUpdate.PuzzleID && s.Team == t).FirstOrDefaultAsync();

                        if (state == null)
                        {
                            context.PuzzleStatePerTeam.Add(new DataModel.PuzzleStatePerTeam()
                            {
                                PuzzleID = puzzleToUpdate.PuzzleID, Team = t, UnlockedTime = unlockTime
                            });
                        }
                        else
                        {
                            state.UnlockedTime = unlockTime;
                        }
                    }
                }
            }

            // after looping through all teams, send one update with all changes made
            await context.SaveChangesAsync();
        }
Exemple #25
0
        /// <summary>
        ///   This routine stores in the database any annotations the requester has uploaded.
        /// </summary>
        private async Task StoreAnnotations(DecodedSyncRequest request, SyncResponse response, int puzzleId, int teamId)
        {
            if (request.AnnotationRequests == null)
            {
                return;
            }

            foreach (var annotationRequest in request.AnnotationRequests)
            {
                // Try to generate this as a new annotation, with version 1.

                Annotation annotation = new Annotation();
                annotation.PuzzleID  = puzzleId;
                annotation.TeamID    = teamId;
                annotation.Key       = annotationRequest.key;
                annotation.Version   = 1;
                annotation.Contents  = annotationRequest.contents;
                annotation.Timestamp = DateTime.Now;

                try {
                    context.Annotations.Add(annotation);
                    await context.SaveChangesAsync();
                }
                catch (DbUpdateException) {
                    // If the insert fails, there must already be an annotation there with the
                    // same puzzle ID, team ID, and key.  So we need to update the existing one.
                    // As we do so, we increment its version number and update its timestamp.
                    //
                    // You may wonder why we're using ExecuteSqlCommandAsync instead of "normal"
                    // Entity Framework database functions.  The answer is that we need to atomically
                    // update the Version field of the record, and Entity Framework has no way of
                    // expressing that directly.
                    //
                    // The reason we want to update the version number atomically is that we rely
                    // on the version number being a unique identifier of an annotation.  We don't
                    // want the following scenario:
                    //
                    // Alice tries to set the annotation for key 17 to A, and simultaneously Bob
                    // tries to set it to B.  Each reads the current version number, finds it to be
                    // 3, and updates the annotation to have version 4.  Both of these updates may
                    // succeed, but one will overwrite the other; let's say Bob's write happens last
                    // and "wins".  So Alice may believe that version 4 is A when actually version 4
                    // is B.  When Alice asks for the current version, she'll be told it's version 4,
                    // and Alice will believe this means it's A.  So Alice will believe that A is
                    // what's stored in the database even though it's not.  Alice and Bob's computers
                    // will display different annotations for the same key, indefinitely.
                    //
                    // Note that we need a version number because the timestamp isn't guaranteed to
                    // be unique.  So in the example above Alice and Bob might wind up updating with
                    // the same timestamp.
                    //
                    // You may also wonder why we use DateTime.Now instead of letting the database
                    // assign the timestamp itself.  The reason is that the database might be running
                    // on a different machine than the puzzle server, and it might be using a different
                    // time zone.

                    // First, detach the annotation from the context so the context doesn't think the annotation is in the database.
                    context.Entry(annotation).State = EntityState.Detached;

                    try {
                        var sqlCommand = "UPDATE Annotations SET Version = Version + 1, Contents = @Contents, Timestamp = @Timestamp WHERE PuzzleID = @PuzzleID AND TeamID = @TeamID AND [Key] = @Key";
                        int result     = await context.Database.ExecuteSqlCommandAsync(sqlCommand,
                                                                                       new SqlParameter("@Contents", annotationRequest.contents),
                                                                                       new SqlParameter("@Timestamp", DateTime.Now),
                                                                                       new SqlParameter("@PuzzleID", puzzleId),
                                                                                       new SqlParameter("@TeamID", teamId),
                                                                                       new SqlParameter("@Key", annotationRequest.key));

                        if (result != 1)
                        {
                            response.AddError("Annotation update failed.");
                        }
                    }
                    catch (DbUpdateException) {
                        response.AddError("Encountered error while trying to update annotation.");
                    }
                    catch (Exception) {
                        response.AddError("Miscellaneous error while trying to update annotation.");
                    }
                }
            }
        }
Exemple #26
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
            });
        }