public EditModel( UserManager <IdentityUser> userManager, PuzzleServerContext context) { _userManager = userManager; _context = context; }
/// <summary> /// Get a read-write query of puzzle state without any missing values. This is the most performant query, but its data is incomplete. Use with caution! /// </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 currently exist in the table.</returns> public static IQueryable <PuzzleStatePerTeam> GetSparseQuery(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) { return(context.PuzzleStatePerTeam.Where(state => state.Puzzle == puzzle && state.Team == team)); } if (puzzle != null) { return(context.PuzzleStatePerTeam.Where(state => state.Puzzle == puzzle)); } if (team != null) { return(context.PuzzleStatePerTeam.Where(state => state.Team == team)); } return(context.PuzzleStatePerTeam.Where(state => state.Puzzle.Event == eventObj)); }
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(); } } }
/// <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(); }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment() && String.IsNullOrEmpty(Environment.GetEnvironmentVariable("WEBSITE_SITE_NAME"))) { app.UseBrowserLink(); app.UseDeveloperExceptionPage(); PuzzleServerContext.UpdateDatabase(app); } else if (env.IsStaging() && Environment.GetEnvironmentVariable("WEBSITE_SITE_NAME") == "PuzzleServerTestDeploy") { app.UseExceptionHandler("/Error"); app.UseHsts(); PuzzleServerContext.UpdateDatabase(app); } else if (env.IsProduction() && (Environment.GetEnvironmentVariable("WEBSITE_SITE_NAME") == "puzzlehunt" || Environment.GetEnvironmentVariable("WEBSITE_SITE_NAME") == "puzzleday")) { app.UseExceptionHandler("/Error"); app.UseHsts(); } else { app.UseExceptionHandler("/Error"); app.UseHsts(); } app.UseHttpsRedirection(); // According to the Identity Scaffolding readme the order of the following calls matters // Must be UseStaticFiles, UseAuthentication, UseMvc app.UseStaticFiles(); app.UseAuthentication(); app.UseMvc(); }
/// <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); } }
/// <summary> /// Returns the the team for the given player /// </summary> /// <param name="dbContext">Current PuzzleServerContext</param> /// <param name="thisEvent">The event that's being checked</param> /// <param name="user">The user being checked</param> /// <returns>The user's team for this event</returns> public static async Task <Team> GetTeamForPlayer(PuzzleServerContext dbContext, Event thisEvent, PuzzleUser user) { if (user == null) { return(null); } return(await dbContext.TeamMembers.Where(t => t.Member.ID == user.ID && t.Team.Event.ID == thisEvent.ID).Select(t => t.Team).FirstOrDefaultAsync()); }
public static async Task SetTeamQualificationAsync( PuzzleServerContext context, Team team, bool value) { team.IsDisqualified = value; await context.SaveChangesAsync(); }
/// <summary> /// Get the solved status of a single puzzle. /// Do not use if you want to get status of many puzzles as it will be very inefficient. /// </summary> /// <returns></returns> public static async Task <bool> IsPuzzleSolved(PuzzleServerContext context, int puzzleID, int teamID) { DateTime?solved = await(from PuzzleStatePerTeam pspt in context.PuzzleStatePerTeam where pspt.PuzzleID == puzzleID && pspt.TeamID == teamID select pspt.SolvedTime).FirstOrDefaultAsync(); return(solved != null); }
public IndexModel( UserManager <IdentityUser> userManager, SignInManager <IdentityUser> signInManager, PuzzleServerContext context) { _userManager = userManager; _signInManager = signInManager; _context = context; }
public static IQueryable <Puzzle> PuzzlesCausingGlobalLockout( PuzzleServerContext context, Event eventObj, Team team) { DateTime now = DateTime.UtcNow; return(PuzzleStateHelper.GetSparseQuery(context, eventObj, null, team) .Where(state => state.SolvedTime == null && state.UnlockedTime != null && state.Puzzle.MinutesOfEventLockout != 0 && state.UnlockedTime.Value + TimeSpan.FromMinutes(state.Puzzle.MinutesOfEventLockout) > now) .Select((s) => s.Puzzle)); }
public ExternalLoginModel( SignInManager <IdentityUser> signInManager, UserManager <IdentityUser> userManager, ILogger <ExternalLoginModel> logger, PuzzleServerContext context) { _signInManager = signInManager; _userManager = userManager; _logger = logger; _context = context; }
/// <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, "")); }
public static async Task CheckForTimedUnlocksAsync( PuzzleServerContext context, Event eventObj, Team team) { DateTime expiry; lock (TimedUnlockExpiryCache) { // throttle this by an expiry interval before we do anything even remotely expensive if (TimedUnlockExpiryCache.TryGetValue(team.ID, out expiry) && expiry >= DateTime.UtcNow) { return; } } DateTime now = DateTime.UtcNow; // do the unlocks in a loop. // The loop will catch cascading unlocks, e.g. if someone does not hit the site between 11:59 and 12:31, catch up to the 12:30 unlocks immediately. while (true) { var puzzlesToSolveByTime = await PuzzleStateHelper.GetSparseQuery(context, eventObj, null, team) .Where(state => state.SolvedTime == null && state.UnlockedTime != null && state.Puzzle.MinutesToAutomaticallySolve != null && state.UnlockedTime.Value + TimeSpan.FromMinutes(state.Puzzle.MinutesToAutomaticallySolve.Value) <= now) .Select((state) => new { Puzzle = state.Puzzle, UnlockedTime = state.UnlockedTime.Value }) .ToListAsync(); foreach (var state in puzzlesToSolveByTime) { // mark solve time as when the puzzle was supposed to complete, so cascading unlocks work properly. await PuzzleStateHelper.SetSolveStateAsync(context, eventObj, state.Puzzle, team, state.UnlockedTime + TimeSpan.FromMinutes(state.Puzzle.MinutesToAutomaticallySolve.Value)); } // get out of the loop if we did nothing if (puzzlesToSolveByTime.Count == 0) { break; } } lock (TimedUnlockExpiryCache) { // effectively, expiry = Math.Max(DateTime.UtcNow, LastGlobalExpiry) + ClosestExpirySpacing - if you could use Math.Max on DateTime expiry = DateTime.UtcNow; if (expiry < LastGlobalExpiry) { expiry = LastGlobalExpiry; } expiry += ClosestExpirySpacing; TimedUnlockExpiryCache[team.ID] = expiry; LastGlobalExpiry = expiry; } }
/// <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(); }
public async Task BindModelAsync(ModelBindingContext bindingContext) { string eventId = bindingContext.ActionContext.RouteData.Values["eventId"] as string; PuzzleServerContext puzzleServerContext = bindingContext.HttpContext.RequestServices.GetService <PuzzleServerContext>(); Event eventObj = await EventHelper.GetEventFromEventId(puzzleServerContext, eventId); if (eventObj != null) { bindingContext.Result = ModelBindingResult.Success(eventObj); } }
public static async Task <Event> GetEventFromContext(AuthorizationHandlerContext context) { Event result = null; if (context.Resource is AuthorizationFilterContext filterContext) { PuzzleServerContext puzzleServerContext = (PuzzleServerContext)filterContext.HttpContext.RequestServices.GetService(typeof(PuzzleServerContext)); string eventId = filterContext.RouteData.Values["eventId"] as string; result = await EventHelper.GetEventFromEventId(puzzleServerContext, eventId); } return(result); }
/// <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(); }
public static async Task <Team> GetTeamFromContext(AuthorizationHandlerContext context) { if (context.Resource is AuthorizationFilterContext filterContext) { string teamIdAsString = filterContext.RouteData.Values["teamId"] as string; if (Int32.TryParse(teamIdAsString, out int teamId)) { PuzzleServerContext puzzleServerContext = (PuzzleServerContext)filterContext.HttpContext.RequestServices.GetService(typeof(PuzzleServerContext)); return(await puzzleServerContext.Teams.Where(e => e.ID == teamId).FirstOrDefaultAsync()); } } return(null); }
public static async Task <List <Puzzle> > GetPuzzles(PuzzleServerContext context, Event Event, PuzzleUser user, EventRole role) { IQueryable <Puzzle> query; if (role == EventRole.admin) { query = context.Puzzles.Where(p => p.Event == Event); } else { query = UserEventHelper.GetPuzzlesForAuthorAndEvent(context, Event, user); } return(await query.OrderBy(p => p.Group).ThenBy(p => p.OrderInGroup).ThenBy(p => p.Name).ToListAsync()); }
/// <summary> /// Returns the event that matches a given eventId /// </summary> /// <param name="eventId">The eventId from the URL - either the ID of an event of the UrlString of an event.</param> public static async Task <Event> GetEventFromEventId(PuzzleServerContext puzzleServerContext, string eventId) { Event result = null; // first, lookup by UrlString - this is the friendly name result = await puzzleServerContext.Events.Where(e => e.UrlString == eventId).FirstOrDefaultAsync(); // otherwise, look up by int for legacy event support // TODO: Delete when people have cleaned up their DBs if (result == null && Int32.TryParse(eventId, out int eventIdAsInt)) { result = await puzzleServerContext.Events.Where(e => e.ID == eventIdAsInt).FirstOrDefaultAsync(); } return(result); }
/// <summary> /// Returns the event that matches a given eventId /// </summary> /// <param name="eventId">The eventId from the URL - either the ID of an event of the UrlString of an event.</param> public static async Task <Event> GetEventFromEventId(PuzzleServerContext puzzleServerContext, string eventId) { Event result = null; // Decide whether to look up by friendly string or ID if (int.TryParse(eventId, out int eventIdAsInt)) { result = await puzzleServerContext.Events.Where(e => e.ID == eventIdAsInt).FirstOrDefaultAsync(); } else { // first, lookup by UrlString - this is the friendly name result = await puzzleServerContext.Events.Where(e => e.UrlString == eventId).FirstOrDefaultAsync(); } return(result); }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseBrowserLink(); app.UseDeveloperExceptionPage(); PuzzleServerContext.UpdateDatabase(app); } else { app.UseExceptionHandler("/Error"); } app.UseStaticFiles(); app.UseMvc(); }
/// <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 static async Task CheckForTimedUnlocksAsync( PuzzleServerContext context, Event eventObj, Team team) { DateTime expiry; lock (TimedUnlockExpiryCache) { // throttle this by an expiry interval before we do anything even remotely expensive if (TimedUnlockExpiryCache.TryGetValue(team.ID, out expiry) && expiry >= DateTime.UtcNow) { return; } } DateTime now = DateTime.UtcNow; var puzzlesToSolveByTime = await PuzzleStateHelper.GetSparseQuery(context, eventObj, null, team) .Where(state => state.SolvedTime == null && state.UnlockedTime != null && state.Puzzle.MinutesToAutomaticallySolve != null && state.UnlockedTime.Value + TimeSpan.FromMinutes(state.Puzzle.MinutesToAutomaticallySolve.Value) <= now) .Select(state => state.Puzzle) .ToListAsync(); foreach (Puzzle puzzle in puzzlesToSolveByTime) { await PuzzleStateHelper.SetSolveStateAsync(context, eventObj, puzzle, team, DateTime.UtcNow); } lock (TimedUnlockExpiryCache) { // effectively, expiry = Math.Max(DateTime.UtcNow, LastGlobalExpiry) + ClosestExpirySpacing - if you could use Math.Max on DateTime expiry = DateTime.UtcNow; if (expiry < LastGlobalExpiry) { expiry = LastGlobalExpiry; } expiry += ClosestExpirySpacing; TimedUnlockExpiryCache[team.ID] = expiry; LastGlobalExpiry = expiry; } }
/// <summary> /// Get a read-only query of puzzle state. You won't be able to write to these values, but the query will be resilient to 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="author">The author; if null get puzzles matching other criteria by all authors</param> /// <returns>A query of PuzzleStatePerTeam objects that can be sorted and instantiated, but you can't edit the results.</returns> public static IQueryable <PuzzleStatePerTeam> GetFullReadOnlyQuery(PuzzleServerContext context, Event eventObj, Puzzle puzzle, Team team, PuzzleUser author = null) { if (context == null) { throw new ArgumentNullException("Context required."); } if (eventObj == null) { throw new ArgumentNullException("Event required."); } if (puzzle != null && team != null) { return(context.PuzzleStatePerTeam .Where(state => state.Puzzle == puzzle && state.Team == team)); } if (puzzle != null) { return(context.PuzzleStatePerTeam.Where(state => state.Puzzle == puzzle)); } if (team != null) { if (author != null) { return(from state in context.PuzzleStatePerTeam join auth in context.PuzzleAuthors on state.PuzzleID equals auth.PuzzleID where state.Team == team && auth.Author == author select state); } else { return(context.PuzzleStatePerTeam.Where(state => state.Team == team)); } } throw new NotImplementedException("Full event query is NYI and may never be needed; use the sparse one"); }
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> /// Get a read-write query of puzzle state without any missing values. This is the most performant query, but its data is incomplete. Use with caution! /// </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 currently exist in the table.</returns> public static IQueryable <PuzzleStatePerTeam> GetSparseQuery(PuzzleServerContext context, Event eventObj, Puzzle puzzle, Team team, PuzzleUser author = null) { if (context == null) { throw new ArgumentNullException("Context required."); } if (eventObj == null) { throw new ArgumentNullException("Event required."); } if (puzzle != null && team != null) { return(context.PuzzleStatePerTeam.Where(state => state.Puzzle == puzzle && state.Team == team)); } if (puzzle != null) { return(context.PuzzleStatePerTeam.Where(state => state.Puzzle == puzzle)); } if (team != null) { if (author != null) { return(from state in context.PuzzleStatePerTeam join auth in context.PuzzleAuthors on state.PuzzleID equals auth.PuzzleID where state.Team == team && auth.Author == author select state); } else { return(context.PuzzleStatePerTeam.Where(state => state.Team == team)); } } return(context.PuzzleStatePerTeam.Where(state => state.Puzzle.Event == eventObj)); }