Refactoring and finished live updates
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Fri, 27 Sep 2019 17:10:25 +0000 (12:10 -0500)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Fri, 27 Sep 2019 17:10:25 +0000 (12:10 -0500)
20 files changed:
codebox/app/app.moon
codebox/controllers/leaderboard/view.moon
codebox/controllers/problem/problem.moon
codebox/controllers/problem/submit.moon
codebox/controllers/submission/list.moon
codebox/controllers/submission/view.moon
codebox/facades/updater.moon
codebox/models/competitions.moon
codebox/models/problems.moon
codebox/models/users.moon
codebox/services/queries.moon [deleted file]
codebox/services/scoring.moon
codebox/static/coffee/leaderboard_update.coffee
codebox/static/js/leaderboard_update.js
codebox/static/js/leaderboard_update.js.map
codebox/views/leaderboard/view.moon
codebox/views/partials/problem_sidebar.moon
codebox/views/ssr/leaderboard.moon
codebox/views/submission/list.moon
updater/app/app.coffee

index dc1750761d5642499d1aaf62ae795e5fb98b2357..07dcfc2e30866a6a8f9522d106a4790d49207bc6 100644 (file)
@@ -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'
 
index 2fdf92642b3adc6ad6e8e611a0e3505b9f426fea..b5f59dbff799dc8b8ebf74b367d1687aebe5c66b 100644 (file)
@@ -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
index 087b9fa29012d8ebf85e14ee95aea431f654ccc0..8db7d4507c7c78d509a100bcbac7ed423e0b722d 100644 (file)
@@ -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: {
index a26efe936c289b1427b7a2b9497853ae0205e5c3..2a55b6757e0b30e4c2800bc61516d224e9c4a908 100644 (file)
@@ -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
index f9484b20bd6e6390e4a6a85c33e3459ea87ee323..36f7eb189b60f2b252aedfb74b9f65474aa4eeae 100644 (file)
@@ -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'
index 9ca09d0264eac919763233ecfef3671911ebe341..4f7531d53317ee66e3cf19c44bc2a8608957a852 100644 (file)
@@ -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' }
index 5557dd3c11bf81f283924b3efd723caae2ec0686..db0a112ed8056414a8b53b56b0c3667de272ecc2 100644 (file)
@@ -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"
index 336e103900e013149a8ee99197deb1e2fbecd69d..033fe3af4f6be4ee2aa7482f1d1960442a4b5a4e 100644 (file)
@@ -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
index f0262f4fa606ff394838fc07cf4727e8512b5057..8db7ba3a6db77fe1215ec6a0c92c2f178f2000db 100644 (file)
@@ -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
index 1b6e47c0227f65af92508eddb96a865af7b36688..f3d01b32bd103452359946b6f00c617761208925 100644 (file)
@@ -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 (file)
index 214eef6..0000000
+++ /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
-}
index a7d705f15445ad45de1921283783ecae033ac634..7c0b757c2e48958f11f479350f4c4686722fbef1 100644 (file)
@@ -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
index 215698295fb7b70b6994bff5e94dca7dca05a8ab..a3ce4066c972476747923bf6f5af48e196d8dd93 100644 (file)
@@ -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()
index 748dde3b81610046558cda2fca85ceab62cae648..2f46a2cd53b9e3070a835c40c4f75cbcddd10825 100644 (file)
@@ -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);
index b4ee4fa0547842e41059f3c233d970a422cccd24..2d0b62af5aae5765990e0c334c894fc719f987f6 100644 (file)
@@ -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
index 4cf3ff50539b5a3d078546dc6bf723200dec7df1..0191b4c5374eaf4eca3d065e89ce69269d26ce69 100644 (file)
@@ -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)
index 4711479a57433e628b87b58b8094a13ed35f8600..9def7ae3c2a4339601f07edf362c0f6a82d3143c 100644 (file)
@@ -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
index 2c5752fccf0cd8a48fd6ac524419a01fcdf05938..a15f1020a014f2fd6d6ca118dde25e3adee4329d 100644 (file)
@@ -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
 
index 255bde516b26a3741db59dda0429b9f81287151d..60e5b695bc45999fa623c9fc995bfeb7f7170bfc 100644 (file)
@@ -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
index 71d9ff211824d710a37fa4fab48c3ed1ca90901f..600f160052518d3ed70ae47df0ff6beb1c060e73 100644 (file)
@@ -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}"