Skip to content

Commit

Permalink
Merge pull request #15 from mcpt/main
Browse files Browse the repository at this point in the history
update cert
  • Loading branch information
JasonLovesDoggo authored Apr 23, 2024
2 parents bc9cebf + 92d4295 commit 2667c73
Show file tree
Hide file tree
Showing 35 changed files with 1,648 additions and 1,409 deletions.
7 changes: 6 additions & 1 deletion .github/workflows/container.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# GitHub recommends pinning actions to a commit SHA.
# Github recommends pinning actions to a commit SHA.
# To get a newer version, you will need to update the SHA.
# You can also reference a tag or branch, but the action may change without warning.

Expand All @@ -7,6 +7,11 @@ name: Create and publish a Docker image
on:
push:

concurrency:
group: ${{ github.workflow }}-${{ github.GITHUB_REF }}
cancel-in-progress: true


env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
Expand Down
4 changes: 3 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,15 @@ RUN set -eux; cd /app/public/scss; mkdir out; for f in *.scss; \
mv out/* .; \
chmod a+r /app/public/scss/*.css

STOPSIGNAL SIGTERM
# Gunicorn listens to SIGTERM
RUN apt-get purge -y sassc && \
rm -rf /var/lib/apt/lists/* && \
rm -rf /var/cache/*
EXPOSE 28730
CMD /app/.venv/bin/gunicorn \
--bind :28730 \
--error-logfile - \
--timeout 120 \
--timeout 20 \
--config /app/container/gunicorn.py \
mCTF.wsgi:application
93 changes: 90 additions & 3 deletions gameserver/admin.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
from adminsortable2.admin import SortableInlineAdminMixin, SortableAdminBase
from adminsortable2.admin import SortableAdminBase, SortableInlineAdminMixin
from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.flatpages.admin import FlatPageAdmin
from django.contrib.flatpages.models import FlatPage
from django.utils.html import format_html
from django.utils.translation import gettext_lazy as _

from . import models
from .models import Submission, ContestSubmission
from .utils.actions import *

User = get_user_model()

Expand Down Expand Up @@ -212,6 +215,7 @@ class ContestAdmin(SortableAdminBase, admin.ModelAdmin):
"summary",
"start_time",
"end_time",
"first_blood_webhook",
"tags",
"max_team_size",
"is_public",
Expand All @@ -225,6 +229,7 @@ class ContestAdmin(SortableAdminBase, admin.ModelAdmin):
"start_time",
"end_time",
]
actions = [recalculate_score]

def has_view_permission(self, request, obj=None):
if request.user.has_perm("gameserver.view_contest"):
Expand Down Expand Up @@ -277,11 +282,92 @@ class UserAdmin(admin.ModelAdmin):
"username",
"full_name",
]
actions = [recalculate_user_scores, recalculate_all_user_scores]


class UserScoreAdmin(admin.ModelAdmin):
fields = (
"user",
"points",
"flag_count",
"last_correct_submission",
"last_correct_submission_obj",
)
list_display = [
"user",
"points",
"last_correct_submission",
]
ordering = ["-points"]
search_fields = [
"user__username",
"user__full_name",
]
readonly_fields = [
"user",
"points",
"flag_count",
"last_correct_submission",
"last_correct_submission_obj",
]

def last_correct_submission_obj(self, obj):
try:
obj = Submission.objects.filter(
user=obj.user, is_correct=True, problem__is_public=True
).latest("date_created")
except Submission.DoesNotExist:
return "No correct submissions"
return format_html("<a href='{url}'>{url}</a>", url=obj.get_absolute_admin_url())

last_correct_submission_obj.allow_tags = True

last_correct_submission_obj.short_description = "Last correct submission URL"


class ContestScoreAdmin(admin.ModelAdmin):
fields = (
"participation",
"points",
"flag_count",
"last_correct_submission",
"last_correct_submission_obj",
)
list_display = [
"participation",
"points",
"last_correct_submission",
]
list_filter = ["participation__contest"]
ordering = ["-points"]
search_fields = [
"participation__team__name",
"participation__team__members__username",
]
readonly_fields = [
"participation",
"points",
"flag_count",
"last_correct_submission",
"last_correct_submission_obj",
]

def last_correct_submission_obj(self, obj):
try:
obj = ContestSubmission.objects.filter(
participation=obj.participation, submission__is_correct=True
).latest("submission__date_created")
except ContestSubmission.DoesNotExist:
return "No correct submissions"
return format_html("<a href='{url}'>{url}</a>", url=obj.get_absolute_admin_url())

last_correct_submission_obj.allow_tags = True
last_correct_submission_obj.short_description = "Last correct submission URL"


admin.site.register(User, UserAdmin)
admin.site.register(models.ContestScore)
admin.site.register(models.UserScore)
admin.site.register(models.ContestScore, ContestScoreAdmin)
admin.site.register(models.UserScore, UserScoreAdmin)
admin.site.register(models.Problem, ProblemAdmin)
admin.site.register(models.Submission, SubmissionAdmin)
admin.site.register(models.ProblemType)
Expand All @@ -295,6 +381,7 @@ class UserAdmin(admin.ModelAdmin):
admin.site.register(models.Contest, ContestAdmin)
admin.site.register(models.ContestTag)
admin.site.register(models.ContestParticipation)
admin.site.register(models.ContestSubmission)
admin.site.site_header = "mCTF administration"
admin.site.site_title = "mCTF admin"

Expand Down
50 changes: 30 additions & 20 deletions gameserver/api/routes.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
from django.db.models import F, OuterRef, Subquery, Case, When, Q
import datetime
from typing import Any, List

from django.db.models import F, OuterRef, Max, Subquery, Case, When, Value, BooleanField, TextField
from django.shortcuts import get_object_or_404
from ninja import NinjaAPI, Schema

from gameserver.models.cache import ContestScore
from gameserver.models.contest import ContestProblem, ContestSubmission, Contest
from ninja import NinjaAPI, Schema
from typing import List, Any
from gameserver.models.profile import User
from gameserver.models.contest import Contest, ContestProblem, ContestSubmission, ContestParticipation

import datetime

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

@staticmethod
def resolve_lastAccept(obj) -> int:
def resolve_lastAccept(obj: dict) -> int:
"""Turns a datetime object into a timestamp."""
if obj["lastAccept"] is None:
print(obj, ' - DEBUG PRINT')
if obj['lastAccept'] is None:
return 0
return int(obj["lastAccept"].timestamp())
return int(obj['lastAccept'].timestamp())

@staticmethod
def resolve_team(obj):
Expand All @@ -60,21 +63,28 @@ def ctftime_standings(request, contest_name: str):
.order_by("-submission__date_created")
.values("submission__date_created")
)

standings = (
ContestScore.ranks(contest=contest_id)
.annotate(
pos=F("rank"),
score=F("points"),
team=F("participation__team__name"),
# team=Coalesce(F("participation__team__name"), F("participation__participants__username")),
# Using Coalesce and indexing
# team=Case(
# When(F("participation__team__isnull")==True, then=Q(("participation__participants")[0]["username"])),
# default=F("team_name"),
# output_field=TextField(),
# ),
lastAccept=Subquery(last_sub_time),
is_solo=Case(
When(participation__team_id=None, then=Value(False)),
default=Value(True),
output_field=BooleanField(),
),
team=Case(
When(participation__team_id=None, then=Subquery( # If the team is None, use the username of the participant ( solo player )
User.objects.filter(contest_participations=OuterRef("participation_id")).values(
"username")[:1]
),),
default=F("participation__team__name"),
output_field=TextField(),
),
lastAccept=Max("participation__submission__submission__date_created"),
)
.filter(score__gt=0)
.values("pos", "score", "team", "lastAccept")
)
task_names = (
Expand All @@ -85,9 +95,9 @@ def ctftime_standings(request, contest_name: str):

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


@api.get("/contests", response=List[ContestOutSchema])
def contests(request):
return (
Contest.objects.filter(is_public=True)
.values("name", "slug", "start_time", "end_time", "max_team_size", "description", "summary")
return Contest.objects.filter(is_public=True).values(
"name", "slug", "start_time", "end_time", "max_team_size", "description", "summary"
)
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Generated by Django 4.0.1 on 2024-03-11 01:49

import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion

import gameserver.models.profile


Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Generated by Django 5.0.4 on 2024-04-04 13:44

from django.db import migrations, models

import gameserver


Expand Down
Loading

0 comments on commit 2667c73

Please sign in to comment.