From: Brendan Hansen Date: Fri, 27 Sep 2019 17:10:25 +0000 (-0500) Subject: Refactoring and finished live updates X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=5f0815ea7d3d6ffaebe184a37fbe02177fed9ac3;p=codebox.git Refactoring and finished live updates --- diff --git a/codebox/app/app.moon b/codebox/app/app.moon index dc17507..07dcfc2 100644 --- a/codebox/app/app.moon +++ b/codebox/app/app.moon @@ -6,7 +6,6 @@ bind\bind_static 'executer', require 'facades.executer' bind\bind_static 'updater', require 'facades.updater' bind\bind_static 'crypto', require 'services.crypto' bind\bind_static 'uuidv4', require 'services.uuid' -bind\bind_static 'queries', require 'services.queries' bind\bind_static 'scoring', require 'services.scoring' bind\bind_static 'time', require 'utils.time' diff --git a/codebox/controllers/leaderboard/view.moon b/codebox/controllers/leaderboard/view.moon index 2fdf926..b5f59db 100644 --- a/codebox/controllers/leaderboard/view.moon +++ b/codebox/controllers/leaderboard/view.moon @@ -5,6 +5,8 @@ import LeaderboardProblems, LeaderboardPlacements from require 'models' make_controller middleware: { 'logged_in', 'competition_started' } + scripts: { 'leaderboard_update' } + raw_scripts: { '/socket.io/socket.io.js' } get: => @navbar.selected = 0 diff --git a/codebox/controllers/problem/problem.moon b/codebox/controllers/problem/problem.moon index 087b9fa..8db7d45 100644 --- a/codebox/controllers/problem/problem.moon +++ b/codebox/controllers/problem/problem.moon @@ -3,9 +3,6 @@ import capture_errors_json, yield_error from require 'lapis.application' import Problems from require 'models' make_controller - inject: - queries: 'queries' - middleware: { 'logged_in', 'competition_started' } scripts: { "pie_chart" } raw_scripts: { diff --git a/codebox/controllers/problem/submit.moon b/codebox/controllers/problem/submit.moon index a26efe9..2a55b67 100644 --- a/codebox/controllers/problem/submit.moon +++ b/codebox/controllers/problem/submit.moon @@ -6,7 +6,6 @@ import Competitions, Problems from require 'models' make_controller inject: - queries: 'queries' executer: 'executer' middleware: { 'logged_in', 'during_competition' } @@ -22,7 +21,7 @@ make_controller @problem = Problems\find short_name: @params.problem_name render: 'problem.submit' - + post: capture_errors_json => assert_valid @params, { { "problem_name", exists: true } @@ -33,7 +32,7 @@ make_controller problem = Problems\find short_name: @params.problem_name unless problem return json: { status: 'problem not found' } - + test_cases = problem\get_test_cases! id = @executer\request @params.lang, @params.code, @user.id, problem.id, @competition.id, test_cases, problem.time_limit diff --git a/codebox/controllers/submission/list.moon b/codebox/controllers/submission/list.moon index f9484b2..36f7eb1 100644 --- a/codebox/controllers/submission/list.moon +++ b/codebox/controllers/submission/list.moon @@ -5,9 +5,6 @@ import capture_errors, yield_error from require 'lapis.application' import Competitions, Problems from require 'models' make_controller - inject: - queries: 'queries' - middleware: { 'logged_in' } scripts: { 'pie_chart' } @@ -18,4 +15,4 @@ make_controller @problem_ids = @competition\get_problem_ids! @problems = [Problems\find id for id in *@problem_ids] - render: 'submission.list' \ No newline at end of file + render: 'submission.list' diff --git a/codebox/controllers/submission/view.moon b/codebox/controllers/submission/view.moon index 9ca09d0..4f7531d 100644 --- a/codebox/controllers/submission/view.moon +++ b/codebox/controllers/submission/view.moon @@ -5,9 +5,6 @@ import capture_errors, capture_errors_json, yield_error from require 'lapis.appl import Competitions, Jobs from require 'models' make_controller - inject: - queries: 'queries' - middleware: { 'logged_in' } scripts: { 'vendor/ace/ace', 'submission_reloader' } raw_scripts: { '/socket.io/socket.io.js' } diff --git a/codebox/facades/updater.moon b/codebox/facades/updater.moon index 5557dd3..db0a112 100644 --- a/codebox/facades/updater.moon +++ b/codebox/facades/updater.moon @@ -4,3 +4,6 @@ http = require 'lapis.nginx.http' class UpdaterFacade push_submission_update: (job_id) => http.simple "#{config.updater_addr}/submission_update?submission_ida=#{job_id}" + + push_leaderboard_update: => + http.simple "#{config.updater_addr}/leaderboard_update" diff --git a/codebox/models/competitions.moon b/codebox/models/competitions.moon index 336e103..033fe3a 100644 --- a/codebox/models/competitions.moon +++ b/codebox/models/competitions.moon @@ -28,3 +28,10 @@ class Competitions extends Model (time.time_to_number @['end']) + @time_offset * 60 } } + + @delete_leaderboard: (competition_id) => + db.query "delete from leaderboard_problems + where leaderboard_placement_id in + (select id from leaderboard_placements where competition_id=?)", competition_id + + db.delete "leaderboard_placements", competition_id: competition_id diff --git a/codebox/models/problems.moon b/codebox/models/problems.moon index f0262f4..8db7ba3 100644 --- a/codebox/models/problems.moon +++ b/codebox/models/problems.moon @@ -48,3 +48,15 @@ class Problems extends Model where competitions.active=TRUE and problem_id=?", @id)[1].count > 0 } } + + @get_codegolf_leaders: (problem_id, competition_id) => + db.select "user_id, problem_id, competition_id, status, time_initiated, char_length(code) as bytes + from jobs + where problem_id=? and competition_id=? + order by status asc, bytes asc, time_initiated asc", problem_id, competition_id + + @clear_codegolf_scores: (problem_id, competition_id) => + db.query "update leaderboard_problems + set points=0, status=1 + from leaderboard_placements + where problem_id=? and leaderboard_placements.competition_id=?", problem_id, competition_id diff --git a/codebox/models/users.moon b/codebox/models/users.moon index 1b6e47c..f3d01b3 100644 --- a/codebox/models/users.moon +++ b/codebox/models/users.moon @@ -1,5 +1,6 @@ import Model from require "lapis.db.model" db = require 'lapis.db' +import Jobs from require 'models' class Users extends Model @relations: { @@ -11,3 +12,46 @@ class Users extends Model where competitions.active=? and jobs.user_id=?", db.TRUE, @id } } + + @has_correct_submission: (user_id, problem_name) => + count = db.select "count(jobs.id) from jobs + inner join problems on problems.id = jobs.problem_id + inner join competitions on jobs.competition_id=competitions.id + where competitions.active=TRUE and jobs.status=? and jobs.user_id=? and problems.short_name=? + ", (Jobs.statuses\for_db 'correct'), user_id, problem_name + + return count[1].count > 0 + + @count_incorrect_submission: (user_id, problem_name) => + count = db.select "count(jobs.id) from jobs + inner join problems on problems.id = jobs.problem_id + inner join competitions on jobs.competition_id=competitions.id + where competitions.active=TRUE and jobs.status in ? and jobs.user_id=? and problems.short_name=? + ", + (db.list { + Jobs.statuses\for_db 'wrong_answer' + Jobs.statuses\for_db 'timed_out' + Jobs.statuses\for_db 'error' + Jobs.statuses\for_db 'compile_err' + }), user_id, problem_name + + return count[1].count + + @has_incorrect_submission: (user_id, problem_name) => + (@@count_incorrect_submission user_id, problem_name) > 0 + + @get_score: (user_id, competition_id) => + res = db.select "sum(points) + from leaderboard_problems + inner join leaderboard_placements on leaderboard_placements.id=leaderboard_problems.leaderboard_placement_id + where leaderboard_placements.user_id=? and competition_id=?", user_id, competition_id + + return 0 if #res == 0 + res[1].sum + + @get_first_correct_submission: (user_id, problem_id, competition_id) => + jobs = db.select "* from jobs where user_id=? and problem_id=? and competition_id=? order by time_initiated asc limit 1", user_id, problem_id, competition_id + jobs[1] + + @get_jobs_by_problem: (user_id, problem_id, competition_id) => + db.select "* from jobs where user_id=? and problem_id=? and competition_id=? order by time_initiated desc", user_id, problem_id, competition_id diff --git a/codebox/services/queries.moon b/codebox/services/queries.moon deleted file mode 100644 index 214eef6..0000000 --- a/codebox/services/queries.moon +++ /dev/null @@ -1,76 +0,0 @@ -db = require 'lapis.db' -import Jobs from require 'models' - -has_correct_submission = (user_id, problem_name) -> - count = db.select "count(jobs.id) from jobs - inner join problems on problems.id = jobs.problem_id - inner join competitions on jobs.competition_id=competitions.id - where competitions.active=TRUE and jobs.status=? and jobs.user_id=? and problems.short_name=? - ", (Jobs.statuses\for_db 'correct'), user_id, problem_name - - return count[1].count > 0 - -count_incorrect_submission = (user_id, problem_name) -> - count = db.select "count(jobs.id) from jobs - inner join problems on problems.id = jobs.problem_id - inner join competitions on jobs.competition_id=competitions.id - where competitions.active=TRUE and jobs.status in ? and jobs.user_id=? and problems.short_name=? - ", - (db.list { - Jobs.statuses\for_db 'wrong_answer' - Jobs.statuses\for_db 'timed_out' - Jobs.statuses\for_db 'error' - Jobs.statuses\for_db 'compile_err' - }), user_id, problem_name - - return count[1].count - -has_incorrect_submission = (user_id, problem_name) -> - count_incorrect_submission(user_id, problem_name) > 0 - -get_jobs_by_user_and_problem_and_competition = (user_id, problem_id, competition_id) -> - db.select "* from jobs where user_id=? and problem_id=? and competition_id=? order by time_initiated desc", user_id, problem_id, competition_id - -get_first_correct_submission = (user_id, problem_id, competition_id) -> - jobs = db.select "* from jobs where user_id=? and problem_id=? and competition_id=? order by time_initiated asc limit 1", user_id, problem_id, competition_id - jobs[1] - -delete_leaderboard_for_competition = (competition_id) -> - db.query "delete from leaderboard_problems - where leaderboard_placement_id in - (select id from leaderboard_placements where competition_id=?)", competition_id - - db.delete "leaderboard_placements", competition_id: competition_id - -get_user_score = (user_id, competition_id) -> - res = db.select "sum(points) - from leaderboard_problems - inner join leaderboard_placements on leaderboard_placements.id=leaderboard_problems.leaderboard_placement_id - where leaderboard_placements.user_id=? and competition_id=?", user_id, competition_id - - return 0 if #res == 0 - res[1].sum - -get_codegolf_leaders = (problem_id, competition_id) -> - db.select "user_id, problem_id, competition_id, status, time_initiated, char_length(code) as bytes - from jobs - where problem_id=? and competition_id=? - order by status asc, bytes asc, time_initiated asc", problem_id, competition_id - -clear_codegolf_scores = (problem_id, competition_id) -> - db.query "update leaderboard_problems - set points=0, status=1 - from leaderboard_placements - where problem_id=? and leaderboard_placements.competition_id=?", problem_id, competition_id - --> { - :has_correct_submission - :count_incorrect_submission - :has_incorrect_submission - :get_jobs_by_user_and_problem_and_competition - :get_first_correct_submission - :delete_leaderboard_for_competition - :get_user_score - :get_codegolf_leaders - :clear_codegolf_scores -} diff --git a/codebox/services/scoring.moon b/codebox/services/scoring.moon index a7d705f..7c0b757 100644 --- a/codebox/services/scoring.moon +++ b/codebox/services/scoring.moon @@ -4,8 +4,8 @@ require 'utils.table' class Scoring extends Injectable new: => - @queries = @make 'queries' @time = @make 'time' + @updater = @make 'updater' -- Get the currently active competition @competition = Competitions\find active: true @@ -20,7 +20,7 @@ class Scoring extends Injectable setup_scoring_tables: => -- Delete all old leaderboard entries for this competition - @queries.delete_leaderboard_for_competition @competition.id + Competitions\delete_leaderboard @competition.id -- Refetch the problems and users in case they have changed @comp_problems = @competition\get_competition_problems! @@ -62,7 +62,7 @@ class Scoring extends Injectable -- Count the incorrect submissions, and if there are -- any, set the status of the problem to be wrong - attempts += @queries.count_incorrect_submission user_id, problem.short_name + attempts += Users\count_incorrect_submission user_id, problem.short_name if attempts > 0 status = LeaderboardProblems.statuses.wrong @@ -70,8 +70,8 @@ class Scoring extends Injectable -- (best for problem worth) and compute the points -- for this problem: -- points = programming_points * worth - 50 * wrong_attempts - if @queries.has_correct_submission user_id, problem.short_name - job = @queries.get_first_correct_submission user_id, problem.id, @competition.id + if Users\has_correct_submission user_id, problem.short_name + job = Users\get_first_correct_submission user_id, problem.id, @competition.id points += math.ceil (@competition.programming_points * @get_problem_worth job.time_initiated) points -= 50 * attempts @@ -87,10 +87,10 @@ class Scoring extends Injectable score_codegolf: (problem) => -- Clear the codegolf scores for this problem in this competition - @queries.clear_codegolf_scores problem.id, @competition.id + Problems\clear_codegolf_scores problem.id, @competition.id -- Get the best code golf submisssions - leaders = @queries.get_codegolf_leaders problem.id, @competition.id + leaders = Problems\get_codegolf_leaders problem.id, @competition.id points = @competition.codegolf_points third_points = points / 3 @@ -138,7 +138,7 @@ class Scoring extends Injectable place: => for u in *@users - u.score = @queries.get_user_score u.id, @competition.id + u.score = Users\get_score u.id, @competition.id table.sort @users, (a, b) -> a.score > b.score @@ -158,6 +158,8 @@ class Scoring extends Injectable place: num score: u.score + @updater\push_leaderboard_update! + rescore_everything: => -- Completely resets everything if a problem is -- added or removed or if a user registers diff --git a/codebox/static/coffee/leaderboard_update.coffee b/codebox/static/coffee/leaderboard_update.coffee index 2156982..a3ce406 100644 --- a/codebox/static/coffee/leaderboard_update.coffee +++ b/codebox/static/coffee/leaderboard_update.coffee @@ -1,2 +1,10 @@ +updateLeaderboard = -> + $.get '/leaderboard/update', {}, (html, _, data) -> + $('#leaderboard-container').html html + $(document).ready -> - console.log "Hello!" + socket = io() + socket.emit "request-leaderboard-updates" + + socket.on 'update', -> + updateLeaderboard() diff --git a/codebox/static/js/leaderboard_update.js b/codebox/static/js/leaderboard_update.js index 748dde3..2f46a2c 100644 --- a/codebox/static/js/leaderboard_update.js +++ b/codebox/static/js/leaderboard_update.js @@ -1,7 +1,20 @@ // Generated by CoffeeScript 2.4.1 (function() { + var updateLeaderboard; + + updateLeaderboard = function() { + return $.get('/leaderboard/update', {}, function(html, _, data) { + return $('#leaderboard-container').html(html); + }); + }; + $(document).ready(function() { - return console.log("Hello!"); + var socket; + socket = io(); + socket.emit("request-leaderboard-updates"); + return socket.on('update', function() { + return updateLeaderboard(); + }); }); }).call(this); diff --git a/codebox/static/js/leaderboard_update.js.map b/codebox/static/js/leaderboard_update.js.map index b4ee4fa..2d0b62a 100644 --- a/codebox/static/js/leaderboard_update.js.map +++ b/codebox/static/js/leaderboard_update.js.map @@ -6,8 +6,8 @@ "coffee/leaderboard_update.coffee" ], "names": [], - "mappings": ";AAAA;EAAA,CAAA,CAAE,QAAF,CAAW,CAAC,KAAZ,CAAkB,QAAA,CAAA,CAAA;WACd,OAAO,CAAC,GAAR,CAAY,QAAZ;EADc,CAAlB;AAAA", + "mappings": ";AAAA;AAAA,MAAA;;EAAA,iBAAA,GAAoB,QAAA,CAAA,CAAA;WACnB,CAAC,CAAC,GAAF,CAAM,qBAAN,EAA6B,CAAA,CAA7B,EAAiC,QAAA,CAAC,IAAD,EAAO,CAAP,EAAU,IAAV,CAAA;aAChC,CAAA,CAAE,wBAAF,CAA2B,CAAC,IAA5B,CAAiC,IAAjC;IADgC,CAAjC;EADmB;;EAIpB,CAAA,CAAE,QAAF,CAAW,CAAC,KAAZ,CAAkB,QAAA,CAAA,CAAA;AACjB,QAAA;IAAA,MAAA,GAAS,EAAA,CAAA;IACT,MAAM,CAAC,IAAP,CAAY,6BAAZ;WAEA,MAAM,CAAC,EAAP,CAAU,QAAV,EAAoB,QAAA,CAAA,CAAA;aACnB,iBAAA,CAAA;IADmB,CAApB;EAJiB,CAAlB;AAJA", "sourcesContent": [ - "$(document).ready ->\n console.log \"Hello!\"\n" + "updateLeaderboard = ->\n\t$.get '/leaderboard/update', {}, (html, _, data) ->\n\t\t$('#leaderboard-container').html html\n\n$(document).ready ->\n\tsocket = io()\n\tsocket.emit \"request-leaderboard-updates\"\n\n\tsocket.on 'update', ->\n\t\tupdateLeaderboard()\n" ] } \ No newline at end of file diff --git a/codebox/views/leaderboard/view.moon b/codebox/views/leaderboard/view.moon index 4cf3ff5..0191b4c 100644 --- a/codebox/views/leaderboard/view.moon +++ b/codebox/views/leaderboard/view.moon @@ -6,5 +6,5 @@ class LeaderboardView extends html.Widget content: => h1 "#{@competition.name} - Leaderboard" - div class: 'content', -> + div id: 'leaderboard-container', class: 'content', -> widget (Leaderboard @placements) diff --git a/codebox/views/partials/problem_sidebar.moon b/codebox/views/partials/problem_sidebar.moon index 4711479..9def7ae 100644 --- a/codebox/views/partials/problem_sidebar.moon +++ b/codebox/views/partials/problem_sidebar.moon @@ -1,5 +1,5 @@ html = require 'lapis.html' -import Competitions from require 'models' +import Competitions, Users from require 'models' class ProblemSidebar extends html.Widget load_problems: => @@ -7,10 +7,10 @@ class ProblemSidebar extends html.Widget @problems = @current_comp\get_problems! for prob in *@problems - if @queries.has_correct_submission @user.id, prob.short_name + if Users\has_correct_submission @user.id, prob.short_name prob.tag = "correct" - elseif @queries.has_incorrect_submission @user.id, prob.short_name - prob.tab = "incorrect" + elseif Users\has_incorrect_submission @user.id, prob.short_name + prob.tag = "incorrect" content: => @load_problems! @@ -20,8 +20,8 @@ class ProblemSidebar extends html.Widget div { selected: prob.short_name == @params.problem_name correct: prob.tag == "correct" - wrong: prob.tab == "incorrect" + wrong: prob.tag == "incorrect" class: 'sidebar-problem' }, -> div class: 'sidebar-problem-letter', -> text prob.letter - div class: 'sidebar-problem-name', -> text prob.name \ No newline at end of file + div class: 'sidebar-problem-name', -> text prob.name diff --git a/codebox/views/ssr/leaderboard.moon b/codebox/views/ssr/leaderboard.moon index 2c5752f..a15f102 100644 --- a/codebox/views/ssr/leaderboard.moon +++ b/codebox/views/ssr/leaderboard.moon @@ -1,5 +1,5 @@ html = require 'lapis.html' -import CompetitionProblems, LeaderboardProblems from require 'models' +import CompetitionProblems, LeaderboardProblems, Problems from require 'models' class Leaderboard extends html.Widget new: (@placements) => @@ -14,6 +14,11 @@ class Leaderboard extends html.Widget flip: true local_key: 'problem_id' where: { competition_id: @competition.id } + Problems\include_in @problems, 'id', + as: 'p' + flip: true + local_key: 'problem_id' + fields: 'id, kind' -- Sort the problems by letter prob.lnum = (prob.cp.letter\byte 1) for prob in *@problems @@ -26,7 +31,10 @@ class Leaderboard extends html.Widget div "Name" div class: 'problem', style: "grid-template-columns: repeat(#{#@problems}, 1fr)", -> for prob in *@problems - div "#{prob.cp.letter}" + div style: 'position: relative', -> + if prob.p.kind == Problems.kinds.golf + span style: "position: absolute; left: 0; top 0; font-size:.8rem", 'Golf' + div "#{prob.cp.letter}" div "Score" drawn_labels = true diff --git a/codebox/views/submission/list.moon b/codebox/views/submission/list.moon index 255bde5..60e5b69 100644 --- a/codebox/views/submission/list.moon +++ b/codebox/views/submission/list.moon @@ -1,5 +1,5 @@ html = require 'lapis.html' -import Jobs from require 'models' +import Jobs, Users from require 'models' class SubmissionList extends html.Widget content: => @@ -9,7 +9,7 @@ class SubmissionList extends html.Widget for prob in *@problems div class: 'header-line', -> div prob.name div class: 'box', -> - jobs = @queries.get_jobs_by_user_and_problem_and_competition @user.id, prob.id, @competition.id + jobs = Users\get_jobs_by_problem @user.id, prob.id, @competition.id if #jobs == 0 div class: 'pad-12', "No submissions to this problem." return diff --git a/updater/app/app.coffee b/updater/app/app.coffee index 71d9ff2..600f160 100644 --- a/updater/app/app.coffee +++ b/updater/app/app.coffee @@ -42,6 +42,7 @@ class UpdateForwarder update_forwarder = new UpdateForwarder() update_forwarder.add_channel "submission-updates" +update_forwarder.add_channel "leaderboard-updates" io.on 'connection', (socket) -> # data is the submission id @@ -49,6 +50,9 @@ io.on 'connection', (socket) -> socket.param = data update_forwarder.join_channel "submission-updates", socket + socket.on 'request-leaderboard-updates', (data) -> + update_forwarder.join_channel "leaderboard-updates", socket + socket.once 'disconnect', -> update_forwarder.leave socket @@ -59,6 +63,12 @@ app.get '/submission_update', (req, res) -> res.status 200 res.end() +app.get '/leaderboard_update', (req, res) -> + update_forwarder.push_update "leaderboard-updates" + + res.status 200 + res.end() + main = -> port = 5000 console.log "Socket IO server running on port #{port}"