mirror of
https://github.com/QingdaoU/OnlineJudge.git
synced 2024-12-29 16:41:56 +00:00
242 lines
9.0 KiB
Python
242 lines
9.0 KiB
Python
import hashlib
|
||
import json
|
||
import os
|
||
import re
|
||
import shutil
|
||
import smtplib
|
||
import time
|
||
from datetime import datetime
|
||
|
||
import pytz
|
||
import requests
|
||
from django.conf import settings
|
||
from django.utils import timezone
|
||
from requests.exceptions import RequestException
|
||
|
||
from account.decorators import super_admin_required
|
||
from account.models import User
|
||
from contest.models import Contest
|
||
from judge.dispatcher import process_pending_task
|
||
from options.options import SysOptions
|
||
from problem.models import Problem
|
||
from submission.models import Submission
|
||
from utils.api import APIView, CSRFExemptAPIView, validate_serializer
|
||
from utils.shortcuts import send_email, get_env
|
||
from utils.xss_filter import XSSHtml
|
||
from .models import JudgeServer
|
||
from .serializers import (CreateEditWebsiteConfigSerializer,
|
||
CreateSMTPConfigSerializer, EditSMTPConfigSerializer,
|
||
JudgeServerHeartbeatSerializer,
|
||
JudgeServerSerializer, TestSMTPConfigSerializer, EditJudgeServerSerializer)
|
||
|
||
|
||
class SMTPAPI(APIView):
|
||
@super_admin_required
|
||
def get(self, request):
|
||
smtp = SysOptions.smtp_config
|
||
if not smtp:
|
||
return self.success(None)
|
||
smtp.pop("password")
|
||
return self.success(smtp)
|
||
|
||
@super_admin_required
|
||
@validate_serializer(CreateSMTPConfigSerializer)
|
||
def post(self, request):
|
||
SysOptions.smtp_config = request.data
|
||
return self.success()
|
||
|
||
@super_admin_required
|
||
@validate_serializer(EditSMTPConfigSerializer)
|
||
def put(self, request):
|
||
smtp = SysOptions.smtp_config
|
||
data = request.data
|
||
for item in ["server", "port", "email", "tls"]:
|
||
smtp[item] = data[item]
|
||
if "password" in data:
|
||
smtp["password"] = data["password"]
|
||
SysOptions.smtp_config = smtp
|
||
return self.success()
|
||
|
||
|
||
class SMTPTestAPI(APIView):
|
||
@super_admin_required
|
||
@validate_serializer(TestSMTPConfigSerializer)
|
||
def post(self, request):
|
||
if not SysOptions.smtp_config:
|
||
return self.error("Please setup SMTP config at first")
|
||
try:
|
||
send_email(smtp_config=SysOptions.smtp_config,
|
||
from_name=SysOptions.website_name_shortcut,
|
||
to_name=request.user.username,
|
||
to_email=request.data["email"],
|
||
subject="You have successfully configured SMTP",
|
||
content="You have successfully configured SMTP")
|
||
except smtplib.SMTPResponseException as e:
|
||
# guess error message encoding
|
||
msg = b"Failed to send email"
|
||
try:
|
||
msg = e.smtp_error
|
||
# qq mail
|
||
msg = msg.decode("gbk")
|
||
except Exception:
|
||
msg = msg.decode("utf-8", "ignore")
|
||
return self.error(msg)
|
||
except Exception as e:
|
||
msg = str(e)
|
||
return self.error(msg)
|
||
return self.success()
|
||
|
||
|
||
class WebsiteConfigAPI(APIView):
|
||
def get(self, request):
|
||
ret = {key: getattr(SysOptions, key) for key in
|
||
["website_base_url", "website_name", "website_name_shortcut",
|
||
"website_footer", "allow_register", "submission_list_show_all"]}
|
||
return self.success(ret)
|
||
|
||
@super_admin_required
|
||
@validate_serializer(CreateEditWebsiteConfigSerializer)
|
||
def post(self, request):
|
||
for k, v in request.data.items():
|
||
if k == "website_footer":
|
||
with XSSHtml() as parser:
|
||
v = parser.clean(v)
|
||
setattr(SysOptions, k, v)
|
||
return self.success()
|
||
|
||
|
||
class JudgeServerAPI(APIView):
|
||
@super_admin_required
|
||
def get(self, request):
|
||
servers = JudgeServer.objects.all().order_by("-last_heartbeat")
|
||
return self.success({"token": SysOptions.judge_server_token,
|
||
"servers": JudgeServerSerializer(servers, many=True).data})
|
||
|
||
@super_admin_required
|
||
def delete(self, request):
|
||
hostname = request.GET.get("hostname")
|
||
if hostname:
|
||
JudgeServer.objects.filter(hostname=hostname).delete()
|
||
return self.success()
|
||
|
||
@validate_serializer(EditJudgeServerSerializer)
|
||
@super_admin_required
|
||
def put(self, request):
|
||
is_disabled = request.data.get("is_disabled", False)
|
||
JudgeServer.objects.filter(id=request.data["id"]).update(is_disabled=is_disabled)
|
||
if not is_disabled:
|
||
process_pending_task()
|
||
return self.success()
|
||
|
||
|
||
class JudgeServerHeartbeatAPI(CSRFExemptAPIView):
|
||
@validate_serializer(JudgeServerHeartbeatSerializer)
|
||
def post(self, request):
|
||
data = request.data
|
||
client_token = request.META.get("HTTP_X_JUDGE_SERVER_TOKEN")
|
||
if hashlib.sha256(SysOptions.judge_server_token.encode("utf-8")).hexdigest() != client_token:
|
||
return self.error("Invalid token")
|
||
|
||
try:
|
||
server = JudgeServer.objects.get(hostname=data["hostname"])
|
||
server.judger_version = data["judger_version"]
|
||
server.cpu_core = data["cpu_core"]
|
||
server.memory_usage = data["memory"]
|
||
server.cpu_usage = data["cpu"]
|
||
server.service_url = data["service_url"]
|
||
server.ip = request.ip
|
||
server.last_heartbeat = timezone.now()
|
||
server.save(update_fields=["judger_version", "cpu_core", "memory_usage", "service_url", "ip", "last_heartbeat"])
|
||
except JudgeServer.DoesNotExist:
|
||
JudgeServer.objects.create(hostname=data["hostname"],
|
||
judger_version=data["judger_version"],
|
||
cpu_core=data["cpu_core"],
|
||
memory_usage=data["memory"],
|
||
cpu_usage=data["cpu"],
|
||
ip=request.META["REMOTE_ADDR"],
|
||
service_url=data["service_url"],
|
||
last_heartbeat=timezone.now(),
|
||
)
|
||
# 新server上线 处理队列中的,防止没有新的提交而导致一直waiting
|
||
process_pending_task()
|
||
|
||
return self.success()
|
||
|
||
|
||
class LanguagesAPI(APIView):
|
||
def get(self, request):
|
||
return self.success({"languages": SysOptions.languages, "spj_languages": SysOptions.spj_languages})
|
||
|
||
|
||
class TestCasePruneAPI(APIView):
|
||
@super_admin_required
|
||
def get(self, request):
|
||
"""
|
||
return orphan test_case list
|
||
"""
|
||
ret_data = []
|
||
dir_to_be_removed = self.get_orphan_ids()
|
||
|
||
# return an iterator
|
||
for d in os.scandir(settings.TEST_CASE_DIR):
|
||
if d.name in dir_to_be_removed:
|
||
ret_data.append({"id": d.name, "create_time": d.stat().st_mtime})
|
||
return self.success(ret_data)
|
||
|
||
@super_admin_required
|
||
def delete(self, request):
|
||
test_case_id = request.GET.get("id")
|
||
if test_case_id:
|
||
self.delete_one(test_case_id)
|
||
return self.success()
|
||
for id in self.get_orphan_ids():
|
||
self.delete_one(id)
|
||
return self.success()
|
||
|
||
@staticmethod
|
||
def get_orphan_ids():
|
||
db_ids = Problem.objects.all().values_list("test_case_id", flat=True)
|
||
disk_ids = os.listdir(settings.TEST_CASE_DIR)
|
||
test_case_re = re.compile(r"^[a-zA-Z0-9]{32}$")
|
||
disk_ids = filter(lambda f: test_case_re.match(f), disk_ids)
|
||
return list(set(disk_ids) - set(db_ids))
|
||
|
||
@staticmethod
|
||
def delete_one(id):
|
||
test_case_dir = os.path.join(settings.TEST_CASE_DIR, id)
|
||
if os.path.isdir(test_case_dir):
|
||
shutil.rmtree(test_case_dir, ignore_errors=True)
|
||
|
||
|
||
class ReleaseNotesAPI(APIView):
|
||
def get(self, request):
|
||
try:
|
||
resp = requests.get("https://raw.githubusercontent.com/QingdaoU/OnlineJudge/master/docs/data.json?_=" + str(time.time()),
|
||
timeout=3)
|
||
releases = resp.json()
|
||
except (RequestException, ValueError):
|
||
return self.success()
|
||
with open("docs/data.json", "r") as f:
|
||
local_version = json.load(f)["update"][0]["version"]
|
||
releases["local_version"] = local_version
|
||
return self.success(releases)
|
||
|
||
|
||
class DashboardInfoAPI(APIView):
|
||
def get(self, request):
|
||
today = datetime.today()
|
||
today_submission_count = Submission.objects.filter(
|
||
create_time__gte=datetime(today.year, today.month, today.day, 0, 0, tzinfo=pytz.UTC)).count()
|
||
recent_contest_count = Contest.objects.exclude(end_time__lt=timezone.now()).count()
|
||
judge_server_count = len(list(filter(lambda x: x.status == "normal", JudgeServer.objects.all())))
|
||
return self.success({
|
||
"user_count": User.objects.count(),
|
||
"recent_contest_count": recent_contest_count,
|
||
"today_submission_count": today_submission_count,
|
||
"judge_server_count": judge_server_count,
|
||
"env": {
|
||
"FORCE_HTTPS": get_env("FORCE_HTTPS", default=False),
|
||
"STATIC_CDN_HOST": get_env("STATIC_CDN_HOST", default="")
|
||
}
|
||
})
|