Skip to content

Commit 2667c73

Browse files
Merge pull request #15 from mcpt/main
update cert
2 parents bc9cebf + 92d4295 commit 2667c73

35 files changed

+1648
-1409
lines changed

.github/workflows/container.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# GitHub recommends pinning actions to a commit SHA.
1+
# Github recommends pinning actions to a commit SHA.
22
# To get a newer version, you will need to update the SHA.
33
# You can also reference a tag or branch, but the action may change without warning.
44

@@ -7,6 +7,11 @@ name: Create and publish a Docker image
77
on:
88
push:
99

10+
concurrency:
11+
group: ${{ github.workflow }}-${{ github.GITHUB_REF }}
12+
cancel-in-progress: true
13+
14+
1015
env:
1116
REGISTRY: ghcr.io
1217
IMAGE_NAME: ${{ github.repository }}

Dockerfile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,15 @@ RUN set -eux; cd /app/public/scss; mkdir out; for f in *.scss; \
3434
mv out/* .; \
3535
chmod a+r /app/public/scss/*.css
3636

37+
STOPSIGNAL SIGTERM
38+
# Gunicorn listens to SIGTERM
3739
RUN apt-get purge -y sassc && \
3840
rm -rf /var/lib/apt/lists/* && \
3941
rm -rf /var/cache/*
4042
EXPOSE 28730
4143
CMD /app/.venv/bin/gunicorn \
4244
--bind :28730 \
4345
--error-logfile - \
44-
--timeout 120 \
46+
--timeout 20 \
4547
--config /app/container/gunicorn.py \
4648
mCTF.wsgi:application

gameserver/admin.py

Lines changed: 90 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
from adminsortable2.admin import SortableInlineAdminMixin, SortableAdminBase
1+
from adminsortable2.admin import SortableAdminBase, SortableInlineAdminMixin
22
from django.contrib import admin
33
from django.contrib.auth import get_user_model
44
from django.contrib.flatpages.admin import FlatPageAdmin
55
from django.contrib.flatpages.models import FlatPage
6+
from django.utils.html import format_html
67
from django.utils.translation import gettext_lazy as _
78

89
from . import models
10+
from .models import Submission, ContestSubmission
11+
from .utils.actions import *
912

1013
User = get_user_model()
1114

@@ -212,6 +215,7 @@ class ContestAdmin(SortableAdminBase, admin.ModelAdmin):
212215
"summary",
213216
"start_time",
214217
"end_time",
218+
"first_blood_webhook",
215219
"tags",
216220
"max_team_size",
217221
"is_public",
@@ -225,6 +229,7 @@ class ContestAdmin(SortableAdminBase, admin.ModelAdmin):
225229
"start_time",
226230
"end_time",
227231
]
232+
actions = [recalculate_score]
228233

229234
def has_view_permission(self, request, obj=None):
230235
if request.user.has_perm("gameserver.view_contest"):
@@ -277,11 +282,92 @@ class UserAdmin(admin.ModelAdmin):
277282
"username",
278283
"full_name",
279284
]
285+
actions = [recalculate_user_scores, recalculate_all_user_scores]
286+
287+
288+
class UserScoreAdmin(admin.ModelAdmin):
289+
fields = (
290+
"user",
291+
"points",
292+
"flag_count",
293+
"last_correct_submission",
294+
"last_correct_submission_obj",
295+
)
296+
list_display = [
297+
"user",
298+
"points",
299+
"last_correct_submission",
300+
]
301+
ordering = ["-points"]
302+
search_fields = [
303+
"user__username",
304+
"user__full_name",
305+
]
306+
readonly_fields = [
307+
"user",
308+
"points",
309+
"flag_count",
310+
"last_correct_submission",
311+
"last_correct_submission_obj",
312+
]
313+
314+
def last_correct_submission_obj(self, obj):
315+
try:
316+
obj = Submission.objects.filter(
317+
user=obj.user, is_correct=True, problem__is_public=True
318+
).latest("date_created")
319+
except Submission.DoesNotExist:
320+
return "No correct submissions"
321+
return format_html("<a href='{url}'>{url}</a>", url=obj.get_absolute_admin_url())
322+
323+
last_correct_submission_obj.allow_tags = True
324+
325+
last_correct_submission_obj.short_description = "Last correct submission URL"
326+
327+
328+
class ContestScoreAdmin(admin.ModelAdmin):
329+
fields = (
330+
"participation",
331+
"points",
332+
"flag_count",
333+
"last_correct_submission",
334+
"last_correct_submission_obj",
335+
)
336+
list_display = [
337+
"participation",
338+
"points",
339+
"last_correct_submission",
340+
]
341+
list_filter = ["participation__contest"]
342+
ordering = ["-points"]
343+
search_fields = [
344+
"participation__team__name",
345+
"participation__team__members__username",
346+
]
347+
readonly_fields = [
348+
"participation",
349+
"points",
350+
"flag_count",
351+
"last_correct_submission",
352+
"last_correct_submission_obj",
353+
]
354+
355+
def last_correct_submission_obj(self, obj):
356+
try:
357+
obj = ContestSubmission.objects.filter(
358+
participation=obj.participation, submission__is_correct=True
359+
).latest("submission__date_created")
360+
except ContestSubmission.DoesNotExist:
361+
return "No correct submissions"
362+
return format_html("<a href='{url}'>{url}</a>", url=obj.get_absolute_admin_url())
363+
364+
last_correct_submission_obj.allow_tags = True
365+
last_correct_submission_obj.short_description = "Last correct submission URL"
280366

281367

282368
admin.site.register(User, UserAdmin)
283-
admin.site.register(models.ContestScore)
284-
admin.site.register(models.UserScore)
369+
admin.site.register(models.ContestScore, ContestScoreAdmin)
370+
admin.site.register(models.UserScore, UserScoreAdmin)
285371
admin.site.register(models.Problem, ProblemAdmin)
286372
admin.site.register(models.Submission, SubmissionAdmin)
287373
admin.site.register(models.ProblemType)
@@ -295,6 +381,7 @@ class UserAdmin(admin.ModelAdmin):
295381
admin.site.register(models.Contest, ContestAdmin)
296382
admin.site.register(models.ContestTag)
297383
admin.site.register(models.ContestParticipation)
384+
admin.site.register(models.ContestSubmission)
298385
admin.site.site_header = "mCTF administration"
299386
admin.site.site_title = "mCTF admin"
300387

gameserver/api/routes.py

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
from django.db.models import F, OuterRef, Subquery, Case, When, Q
1+
import datetime
2+
from typing import Any, List
3+
4+
from django.db.models import F, OuterRef, Max, Subquery, Case, When, Value, BooleanField, TextField
25
from django.shortcuts import get_object_or_404
6+
from ninja import NinjaAPI, Schema
37

48
from gameserver.models.cache import ContestScore
5-
from gameserver.models.contest import ContestProblem, ContestSubmission, Contest
6-
from ninja import NinjaAPI, Schema
7-
from typing import List, Any
9+
from gameserver.models.profile import User
10+
from gameserver.models.contest import Contest, ContestProblem, ContestSubmission, ContestParticipation
811

9-
import datetime
1012

1113
def unicode_safe(string):
1214
return string.encode("unicode_escape").decode()
@@ -32,11 +34,12 @@ class CTFSchema(Schema):
3234
lastAccept: Any = None
3335

3436
@staticmethod
35-
def resolve_lastAccept(obj) -> int:
37+
def resolve_lastAccept(obj: dict) -> int:
3638
"""Turns a datetime object into a timestamp."""
37-
if obj["lastAccept"] is None:
39+
print(obj, ' - DEBUG PRINT')
40+
if obj['lastAccept'] is None:
3841
return 0
39-
return int(obj["lastAccept"].timestamp())
42+
return int(obj['lastAccept'].timestamp())
4043

4144
@staticmethod
4245
def resolve_team(obj):
@@ -60,21 +63,28 @@ def ctftime_standings(request, contest_name: str):
6063
.order_by("-submission__date_created")
6164
.values("submission__date_created")
6265
)
66+
6367
standings = (
6468
ContestScore.ranks(contest=contest_id)
6569
.annotate(
6670
pos=F("rank"),
6771
score=F("points"),
68-
team=F("participation__team__name"),
69-
# team=Coalesce(F("participation__team__name"), F("participation__participants__username")),
70-
# Using Coalesce and indexing
71-
# team=Case(
72-
# When(F("participation__team__isnull")==True, then=Q(("participation__participants")[0]["username"])),
73-
# default=F("team_name"),
74-
# output_field=TextField(),
75-
# ),
76-
lastAccept=Subquery(last_sub_time),
72+
is_solo=Case(
73+
When(participation__team_id=None, then=Value(False)),
74+
default=Value(True),
75+
output_field=BooleanField(),
76+
),
77+
team=Case(
78+
When(participation__team_id=None, then=Subquery( # If the team is None, use the username of the participant ( solo player )
79+
User.objects.filter(contest_participations=OuterRef("participation_id")).values(
80+
"username")[:1]
81+
),),
82+
default=F("participation__team__name"),
83+
output_field=TextField(),
84+
),
85+
lastAccept=Max("participation__submission__submission__date_created"),
7786
)
87+
.filter(score__gt=0)
7888
.values("pos", "score", "team", "lastAccept")
7989
)
8090
task_names = (
@@ -85,9 +95,9 @@ def ctftime_standings(request, contest_name: str):
8595

8696
return {"standings": standings, "tasks": task_names}
8797

98+
8899
@api.get("/contests", response=List[ContestOutSchema])
89100
def contests(request):
90-
return (
91-
Contest.objects.filter(is_public=True)
92-
.values("name", "slug", "start_time", "end_time", "max_team_size", "description", "summary")
101+
return Contest.objects.filter(is_public=True).values(
102+
"name", "slug", "start_time", "end_time", "max_team_size", "description", "summary"
93103
)

gameserver/migrations/0033_contestscore_userscore_alter_user_timezone_and_more.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
# Generated by Django 4.0.1 on 2024-03-11 01:49
22

3+
import django.db.models.deletion
34
from django.conf import settings
45
from django.db import migrations, models
5-
import django.db.models.deletion
6+
67
import gameserver.models.profile
78

89

gameserver/migrations/0034_alter_contestscore_options_alter_userscore_options_and_more.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Generated by Django 5.0.4 on 2024-04-04 13:44
22

33
from django.db import migrations, models
4+
45
import gameserver
56

67

0 commit comments

Comments
 (0)