Skip to content

Commit

Permalink
Final race fixes + cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
jdabtieu committed Dec 25, 2023
1 parent 2bf8fdf commit e21af26
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 43 deletions.
2 changes: 1 addition & 1 deletion src/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ def dl_contest(contest_id, problem_id):
if len(contest) == 0:
return abort(404)
# Ensure contest started or user is admin
start = datetime.strptime(contest[0]["start"], "%Y-%m-%d %H:%M:%S")
start = parse_datetime(contest[0]["start"])
if datetime.utcnow() < start and not check_perm(["ADMIN", "SUPERADMIN", "CONTENT_MANAGER"]):
return abort(404)
problem = db.execute(("SELECT * FROM contest_problems WHERE contest_id=? "
Expand Down
22 changes: 13 additions & 9 deletions src/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,14 +246,13 @@ def create_jwt(data, secret_key, time=1800):
return jwt.encode(data, secret_key, algorithm='HS256')


def update_dyn_score(contest_id, problem_id, update_curr_user=True, transact=True, d_solves=1):
def update_dyn_score(contest_id, problem_id, update_curr_user=True, d_solves=1):
"""
Updates the dynamic scoring of contest_id/problem_id, using the db object
Updates the dynamic scoring of contest_id/problem_id, using the db object.
A transaction must be started before calling this function.
For details see: https://www.desmos.com/calculator/eifeir81wk
https://github.com/jdabtieu/CTFOJ/issues/2
"""
if transact:
db.execute("BEGIN")
if update_curr_user:
db.execute(("INSERT INTO contest_solved(contest_id, user_id, problem_id) "
"VALUES(:cid, :uid, :pid)"),
Expand Down Expand Up @@ -289,8 +288,6 @@ def update_dyn_score(contest_id, problem_id, update_curr_user=True, transact=Tru
"(SELECT user_id FROM contest_solved WHERE "
"contest_id=:cid AND problem_id=:pid)"),
point_change=point_diff, cid=contest_id, pid=problem_id)
if transact:
db.execute("COMMIT")


def contest_exists(contest_id):
Expand Down Expand Up @@ -356,13 +353,20 @@ def register_chk(username, password, confirmation, email):
return 0


def parse_datetime(s):
"""
Parses a datetime stored in the database into a Python datetime object
"""
return datetime.strptime(s, "%Y-%m-%d %H:%M:%S")


def contest_ended(info):
"""
Determine if the contest from db query info has ended
Returns whether the contest is over or not
info should be an array (len 1) of a dict representing the contest data from the db
"""
end = datetime.strptime(info[0]["end"], "%Y-%m-%d %H:%M:%S")
end = parse_datetime(info[0]["end"])
return datetime.utcnow() > end


Expand All @@ -388,7 +392,7 @@ def rejudge_contest_problem(contest_id, problem_id, new_flag):
db.execute("DELETE FROM contest_solved WHERE contest_id=? AND problem_id=?",
contest_id, problem_id)
if data["score_users"] >= 0: # Reset dynamic scoring
update_dyn_score(contest_id, problem_id, False, False)
update_dyn_score(contest_id, problem_id, False)

# Set all new correct submissions
db.execute(("UPDATE submissions SET correct=1 WHERE contest_id=? AND "
Expand All @@ -407,7 +411,7 @@ def rejudge_contest_problem(contest_id, problem_id, new_flag):
old_points = data["point_value"]
else: # Instructions for dynamic scoring
old_points = data["score_max"]
update_dyn_score(contest_id, problem_id, False, False)
update_dyn_score(contest_id, problem_id, False)
db.execute(("UPDATE contest_users SET points=points+? WHERE user_id IN (SELECT "
"user_id FROM contest_solved WHERE contest_id=? AND problem_id=?)"),
old_points, contest_id, problem_id)
Expand Down
4 changes: 2 additions & 2 deletions src/views/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def check_instancer_perms(id):
contest = db.execute("SELECT * FROM contests WHERE id=?", contest_id)
if len(contest) != 1:
return ("Contest not found", 404)
start = datetime.strptime(contest[0]["start"], "%Y-%m-%d %H:%M:%S")
start = parse_datetime(contest[0]["start"])
has_perm = api_perm(["ADMIN", "SUPERADMIN", "CONTENT_MANAGER"])
if datetime.utcnow() < start and not has_perm:
return ("The contest has not started", 403)
Expand Down Expand Up @@ -174,7 +174,7 @@ def contest_problem():
contest = db.execute("SELECT * FROM contests WHERE id=?", contest_id)
if len(contest) != 1:
return json_fail("Contest not found", 404)
start = datetime.strptime(contest[0]["start"], "%Y-%m-%d %H:%M:%S")
start = parse_datetime(contest[0]["start"])
has_perm = api_perm(["ADMIN", "SUPERADMIN", "CONTENT_MANAGER"])
if datetime.utcnow() < start and not has_perm:
return json_fail("The contest has not started", 403)
Expand Down
42 changes: 21 additions & 21 deletions src/views/contest.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,20 @@ def contest(contest_id):
return render_template("contest/contest_noexist.html"), 404

# Ensure contest started or user is admin
start = datetime.strptime(contest_info[0]["start"], "%Y-%m-%d %H:%M:%S")
start = parse_datetime(contest_info[0]["start"])
if datetime.utcnow() < start and not check_perm(["ADMIN", "SUPERADMIN"]):
flash('The contest has not started yet!', 'danger')
return redirect("/contests")

title = contest_info[0]["name"]
scoreboard_key = contest_info[0]["scoreboard_key"]

db.execute("BEGIN")
user_info = db.execute(
"SELECT * FROM contest_users WHERE contest_id=:cid AND user_id=:uid",
cid=contest_id, uid=session["user_id"])

if len(user_info) == 0 and datetime.utcnow() < datetime.strptime(contest_info[0]["end"], "%Y-%m-%d %H:%M:%S"):
if len(user_info) == 0 and datetime.utcnow() < parse_datetime(contest_info[0]["end"]):
db.execute("INSERT INTO contest_users (contest_id, user_id) VALUES(:cid, :uid)",
cid=contest_id, uid=session["user_id"])
db.execute("UPDATE users SET contests_completed=contests_completed+1 WHERE id=?",
Expand All @@ -66,6 +67,7 @@ def contest(contest_id):
"SELECT user_id FROM contest_users WHERE contest_id=:cid AND "
"hidden != 0) GROUP BY problem_id"), cid=contest_id)
solve_count = {x["problem_id"]: x["solves"] for x in solves}
db.execute("COMMIT")

for row in info:
problem_id = row["problem_id"]
Expand Down Expand Up @@ -212,8 +214,8 @@ def contest_problem(contest_id, problem_id):

# Ensure contest started or user is admin
check = db.execute("SELECT * FROM contests WHERE id=?", contest_id)
start = datetime.strptime(check[0]["start"], "%Y-%m-%d %H:%M:%S")
if datetime.utcnow() < start and not check_perm(["ADMIN", "SUPERADMIN", "CONTENT_MANAGER"]):
if (datetime.utcnow() < parse_datetime(check[0]["start"])
and not check_perm(["ADMIN", "SUPERADMIN", "CONTENT_MANAGER"])):
flash('The contest has not started yet!', 'danger')
return redirect("/contests")

Expand All @@ -224,10 +226,10 @@ def contest_problem(contest_id, problem_id):
return render_template("contest/contest_problem_noexist.html"), 404

# Check if problem is solved
check[0]["solved"] = len(db.execute(
("SELECT * FROM contest_solved WHERE contest_id=:cid AND "
"problem_id=:pid AND user_id=:uid"),
cid=contest_id, pid=problem_id, uid=session["user_id"])) == 1
check[0]["solved"] = db.execute(
("SELECT COUNT(*) AS cnt FROM contest_solved WHERE contest_id=? AND "
"problem_id=? AND user_id=?"),
contest_id, problem_id, session["user_id"])[0]["cnt"]

if request.method == "GET":
return render_template("contest/contest_problem.html", data=check[0])
Expand Down Expand Up @@ -265,6 +267,7 @@ def contest_problem(contest_id, problem_id):
return render_template("contest/contest_problem.html", data=check[0])

# Check if user has already found this flag
db.execute("BEGIN")
check1 = db.execute(("SELECT * FROM contest_solved WHERE contest_id=:cid "
"AND user_id=:uid AND problem_id=:pid"),
cid=contest_id, uid=session["user_id"], pid=problem_id)
Expand All @@ -280,6 +283,7 @@ def contest_problem(contest_id, problem_id):
db.execute(("UPDATE contest_users SET lastAC=datetime('now'), "
"points=points+:points WHERE contest_id=:cid AND user_id=:uid"),
cid=contest_id, points=points, uid=session["user_id"])
db.execute("COMMIT")

flash('Congratulations! You have solved this problem!', 'success')
return redirect(f"/contest/{contest_id}/problem/{problem_id}")
Expand Down Expand Up @@ -314,7 +318,7 @@ def edit_contest_problem(contest_id, problem_id):
data = db.execute(
"SELECT * FROM contest_problems WHERE contest_id=:cid AND problem_id=:pid",
cid=contest_id, pid=problem_id)
if len(data) != 1:
if len(data) == 0:
return render_template("contest/contest_problem_noexist.html"), 404

if request.method == "GET":
Expand All @@ -323,14 +327,12 @@ def edit_contest_problem(contest_id, problem_id):
# Reached via POST

new_name = request.form.get("name")
new_description = request.form.get("description")
new_hint = request.form.get("hints")
new_description = (request.form.get("description") or "").replace('\r', '')
new_hint = (request.form.get("hints") or "")
new_category = request.form.get("category")
new_points = request.form.get("point_value")
new_flag = request.form.get("flag")
new_flag_hint = request.form.get("flag_hint")
if not new_flag_hint:
new_flag_hint = ""
new_flag_hint = (request.form.get("flag_hint") or "")
new_instanced = bool(request.form.get("instanced"))

if (not new_name or not new_description or not new_category
Expand All @@ -348,20 +350,18 @@ def edit_contest_problem(contest_id, problem_id):
new_flag = data[0]["flag"]
new_flag_hint = data[0]["flag_hint"]

new_description = new_description.replace('\r', '')
if not new_hint:
new_hint = ""

# Only edit score for statically scored problems whose value has changed
if data[0]["score_users"] == -1 and data[0]["point_value"] != new_points:
point_change = int(new_points) - data[0]["point_value"]
db.execute("BEGIN")
db.execute(("UPDATE contest_users SET points=points+:point_change WHERE "
"contest_id=:cid AND user_id IN (SELECT user_id FROM contest_solved "
"WHERE contest_id=:cid AND problem_id=:pid)"),
point_change=point_change, cid=contest_id, pid=problem_id)
db.execute(("UPDATE contest_problems SET point_value=:pv WHERE contest_id=:cid "
"AND problem_id=:pid"),
pv=int(new_points), cid=contest_id, pid=problem_id)
db.execute("COMMIT")

db.execute(("UPDATE contest_problems SET name=:name, category=:category, flag=:flag, "
"flag_hint=:fhint, instanced=:inst WHERE contest_id=:cid AND problem_id=:pid"),
Expand All @@ -385,7 +385,7 @@ def edit_contest_problem(contest_id, problem_id):
f"{problem_id} in contest {contest_id}"),
extra={"section": "contest"})
flash('Problem successfully edited', 'success')
return redirect("/contest/" + contest_id + "/problem/" + problem_id)
return redirect(f"/contest/{contest_id}/problem/{problem_id}")


@api.route("/<contest_id>/scoreboard")
Expand Down Expand Up @@ -457,7 +457,7 @@ def _contest_hide(contest_id, hide_enum, hide_msg):
"contest_problems.contest_id=? AND contest_problems.score_users != -1"),
user_id, contest_id, contest_id)
for problem in updatelist:
update_dyn_score(contest_id, problem["problem_id"], False, False, -1)
update_dyn_score(contest_id, problem["problem_id"], False, -1)

db.execute("COMMIT")
flash(f"User successfully {hide_msg}!", "success")
Expand Down Expand Up @@ -499,7 +499,7 @@ def _contest_unhide(contest_id, hide_msg):
"contest_problems.contest_id=? AND contest_problems.score_users != -1"),
user_id, contest_id, contest_id)
for problem in updatelist:
update_dyn_score(contest_id, problem["problem_id"], False, False)
update_dyn_score(contest_id, problem["problem_id"], False)

db.execute("COMMIT")
flash(f"User successfully un{hide_msg}!", "success")
Expand Down
14 changes: 4 additions & 10 deletions src/views/problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,12 @@ def editproblem(problem_id):
# Reached via POST

new_name = request.form.get("name")
new_description = request.form.get("description")
new_hint = request.form.get("hints")
new_description = (request.form.get("description") or "").replace('\r', '')
new_hint = (request.form.get("hints") or "")
new_category = request.form.get("category")
new_points = int(request.form.get("point_value"))
new_flag = request.form.get("flag")
new_flag_hint = request.form.get("flag_hint")
if not new_flag_hint:
new_flag_hint = ""
new_flag_hint = (request.form.get("flag_hint") or "")
new_instanced = bool(request.form.get("instanced"))

if not new_name or not new_description or not new_category or not new_points:
Expand Down Expand Up @@ -161,10 +159,6 @@ def editproblem(problem_id):
new_flag = data[0]["flag"]
new_flag_hint = data[0]["flag_hint"]

new_description = new_description.replace('\r', '')
if not new_hint:
new_hint = ""

db.execute(("UPDATE problems SET name=:name, category=:category, point_value=:pv, "
"flag=:flag, flag_hint=:fhint, instanced=:inst WHERE id=:problem_id"),
name=new_name, category=new_category, pv=new_points,
Expand All @@ -187,7 +181,7 @@ def editproblem(problem_id):
write_file('metadata/problems/' + problem_id + '/description.md', new_description)
write_file('metadata/problems/' + problem_id + '/hints.md', new_hint)

logger.info((f"User #{session['user_id']} ({session['username']}) updated problem "
logger.info((f"User #{session['user_id']} ({session['username']}) edited problem "
f"{problem_id}"), extra={"section": "problem"})
flash('Problem successfully edited', 'success')
return redirect("/problem/" + problem_id)
Expand Down

1 comment on commit e21af26

@jdabtieu
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.