diff --git a/services/backend/api/v1/task/schemas.py b/services/backend/api/v1/task/schemas.py
index 3e07b92..a203f22 100644
--- a/services/backend/api/v1/task/schemas.py
+++ b/services/backend/api/v1/task/schemas.py
@@ -62,3 +62,8 @@ class TaskAttachmentSchema(ModelSchema):
"file",
"public",
)
+
+
+class TaskStatusSchema(Schema):
+ task_name: str
+ result: int
diff --git a/services/backend/api/v1/task/views.py b/services/backend/api/v1/task/views.py
index f2e93c9..137e73e 100644
--- a/services/backend/api/v1/task/views.py
+++ b/services/backend/api/v1/task/views.py
@@ -1,7 +1,7 @@
from http import HTTPStatus as status
from uuid import UUID
-from django.shortcuts import get_object_or_404
+from django.shortcuts import get_object_or_404, get_list_or_404
from ninja import File, Router, UploadedFile
from api.v1.ping.schemas import PingOut
@@ -11,6 +11,7 @@ from api.v1.task.schemas import (
TaskAttachmentSchema,
TaskOutSchema,
TaskSubmissionOut,
+ TaskStatusSchema,
)
from apps.achievement.models import Achievement, UserAchievement
from apps.competition.models import State
@@ -116,14 +117,16 @@ def submit_task(
return status.FORBIDDEN, ForbiddenError()
if task.type == CompetitionTask.CompetitionTaskType.INPUT:
+ verdict = content.read() == task.correct_answer_file.read()
submission = CompetitionTaskSubmission.objects.create(
user=user,
task=task,
status=CompetitionTaskSubmission.StatusChoices.CHECKED,
content=content,
result={
- "correct": content.read() == task.correct_answer_file.read()
+ "correct": verdict
},
+ earned_points=task.points
)
if task.type == CompetitionTask.CompetitionTaskType.REVIEW:
submission = CompetitionTaskSubmission.objects.create(
@@ -173,3 +176,31 @@ def get_submissions_history(request, competition_id: UUID, task_id: UUID):
def get_task_attachments(request, competition_id: UUID, task_id: UUID):
task = get_object_or_404(CompetitionTask, id=task_id)
return status.OK, CompetitionTaskAttachment.objects.filter(task=task).all()
+
+
+@router.get(
+ "competitions/{competition_id}/results",
+ response={
+ status.OK: list[TaskStatusSchema],
+ status.UNAUTHORIZED: UnauthorizedError
+ },
+)
+def get_competition_results(request, competition_id: UUID):
+ tasks = get_list_or_404(CompetitionTask, competition_id=competition_id)
+
+ data = []
+
+ for task in tasks:
+ submissions = CompetitionTaskSubmission.objects.filter(
+ user=request.auth, task=task
+ ).filter(status="checked").all()
+ if not submissions:
+ result = 0
+ else:
+ result = submissions[0].earned_points
+ data.append(TaskStatusSchema(
+ task_name=task.title,
+ result=result
+ ))
+
+ return status.OK, data
diff --git a/services/backend/apps/task/admin.py b/services/backend/apps/task/admin.py
index ca097c0..af2477b 100644
--- a/services/backend/apps/task/admin.py
+++ b/services/backend/apps/task/admin.py
@@ -42,7 +42,7 @@ class CompetitionTaskSubmissionAdmin(admin.ModelAdmin):
"user__username",
"user__email",
)
- list_filter = ("plagiarism_checked", "status")
+ list_filter = ("plagiarism_detected", "status")
ordering = ["-timestamp"]
def has_add_permission(self, request, obj=None):
diff --git a/services/backend/apps/task/migrations/0002_remove_competitiontasksubmission_plagiarism_checked_and_more.py b/services/backend/apps/task/migrations/0002_remove_competitiontasksubmission_plagiarism_checked_and_more.py
new file mode 100644
index 0000000..01c63d2
--- /dev/null
+++ b/services/backend/apps/task/migrations/0002_remove_competitiontasksubmission_plagiarism_checked_and_more.py
@@ -0,0 +1,22 @@
+# Generated by Django 5.1.6 on 2025-03-03 13:12
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('task', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='competitiontasksubmission',
+ name='plagiarism_checked',
+ ),
+ migrations.AddField(
+ model_name='competitiontasksubmission',
+ name='plagiarism_detected',
+ field=models.BooleanField(default=False, verbose_name='обнаружен плагиат'),
+ ),
+ ]
diff --git a/services/backend/apps/task/models.py b/services/backend/apps/task/models.py
index 8f6936e..77123b2 100644
--- a/services/backend/apps/task/models.py
+++ b/services/backend/apps/task/models.py
@@ -178,8 +178,8 @@ class CompetitionTaskSubmission(BaseModel):
checked_at = models.DateTimeField(
null=True, blank=True, verbose_name="дата проверки"
)
- plagiarism_checked = models.BooleanField(
- default=False, verbose_name="проверено на плагиат"
+ plagiarism_detected = models.BooleanField(
+ default=False, verbose_name="обнаружен плагиат"
)
timestamp = models.DateTimeField(
auto_now_add=True, verbose_name="дата отправки"
diff --git a/services/backend/apps/task/tasks.py b/services/backend/apps/task/tasks.py
index 86588f4..b68ecf2 100644
--- a/services/backend/apps/task/tasks.py
+++ b/services/backend/apps/task/tasks.py
@@ -4,6 +4,7 @@ import httpx
from celery import shared_task
from django.conf import settings
from django.core.files.base import ContentFile
+from urllib.parse import urlparse
from apps.task.models import CompetitionTaskSubmission
@@ -12,10 +13,16 @@ from apps.task.models import CompetitionTaskSubmission
def analyze_data_task(self, submission_id):
submission = CompetitionTaskSubmission.objects.get(id=submission_id)
try:
- code_url = f"{settings.MINIO_DEFAULT_CUSTOM_ENDPOINT_URL}{submission.content.path}"
+ code_url = (
+ f"{settings.MINIO_DEFAULT_CUSTOM_ENDPOINT_URL}/"
+ f"{urlparse(submission.content.url).path}"
+ )
files = [
{
- "url": f"{settings.MINIO_DEFAULT_CUSTOM_ENDPOINT_URL}{attachment.path}",
+ "url": (
+ f"{settings.MINIO_DEFAULT_CUSTOM_ENDPOINT_URL}/"
+ f"{urlparse(submission.content.url).path}"
+ ),
"bind_path": attachment.bind_at,
}
for attachment in submission.task.attachments.filter(
@@ -40,12 +47,12 @@ def analyze_data_task(self, submission_id):
submission.stdout.save("output.txt", ContentFile(result["output"]))
submission.result = {
- "correct": result["hash_match"],
- "result_hash": result["result_hash"],
+ "correct": result["correct"],
+ "hash_match": result["hash_match"],
"error": result.get("error"),
}
submission.earned_points = (
- submission.task.points if result["hash_match"] else 0
+ submission.task.points if result["correct"] else 0
)
submission.status = CompetitionTaskSubmission.StatusChoices.CHECKED
diff --git a/services/frontend/bun.lock b/services/frontend/bun.lock
index c5f880c..3448773 100644
--- a/services/frontend/bun.lock
+++ b/services/frontend/bun.lock
@@ -30,6 +30,7 @@
"react-router": "^7.2.0",
"react-router-dom": "^7.2.0",
"rehype-katex": "^7.0.1",
+ "remark-gfm": "^4.0.1",
"remark-math": "^6.0.0",
"tailwind-merge": "^3.0.2",
"tailwindcss": "^4.0.9",
@@ -605,8 +606,24 @@
"lucide-react": ["lucide-react@0.476.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-x6cLTk8gahdUPje0hSgLN1/MgiJH+Xl90Xoxy9bkPAsMPOUiyRSKR4JCDPGVCEpyqnZXH3exFWNItcvra9WzUQ=="],
+ "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="],
+
+ "mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="],
+
"mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="],
+ "mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="],
+
+ "mdast-util-gfm-autolink-literal": ["mdast-util-gfm-autolink-literal@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "ccount": "^2.0.0", "devlop": "^1.0.0", "mdast-util-find-and-replace": "^3.0.0", "micromark-util-character": "^2.0.0" } }, "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ=="],
+
+ "mdast-util-gfm-footnote": ["mdast-util-gfm-footnote@2.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0" } }, "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ=="],
+
+ "mdast-util-gfm-strikethrough": ["mdast-util-gfm-strikethrough@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg=="],
+
+ "mdast-util-gfm-table": ["mdast-util-gfm-table@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "markdown-table": "^3.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg=="],
+
+ "mdast-util-gfm-task-list-item": ["mdast-util-gfm-task-list-item@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ=="],
+
"mdast-util-math": ["mdast-util-math@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "longest-streak": "^3.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.1.0", "unist-util-remove-position": "^5.0.0" } }, "sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w=="],
"mdast-util-mdx-expression": ["mdast-util-mdx-expression@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ=="],
@@ -629,6 +646,20 @@
"micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="],
+ "micromark-extension-gfm": ["micromark-extension-gfm@3.0.0", "", { "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", "micromark-extension-gfm-footnote": "^2.0.0", "micromark-extension-gfm-strikethrough": "^2.0.0", "micromark-extension-gfm-table": "^2.0.0", "micromark-extension-gfm-tagfilter": "^2.0.0", "micromark-extension-gfm-task-list-item": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w=="],
+
+ "micromark-extension-gfm-autolink-literal": ["micromark-extension-gfm-autolink-literal@2.1.0", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw=="],
+
+ "micromark-extension-gfm-footnote": ["micromark-extension-gfm-footnote@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw=="],
+
+ "micromark-extension-gfm-strikethrough": ["micromark-extension-gfm-strikethrough@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw=="],
+
+ "micromark-extension-gfm-table": ["micromark-extension-gfm-table@2.1.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg=="],
+
+ "micromark-extension-gfm-tagfilter": ["micromark-extension-gfm-tagfilter@2.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg=="],
+
+ "micromark-extension-gfm-task-list-item": ["micromark-extension-gfm-task-list-item@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw=="],
+
"micromark-extension-math": ["micromark-extension-math@3.1.0", "", { "dependencies": { "@types/katex": "^0.16.0", "devlop": "^1.0.0", "katex": "^0.16.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg=="],
"micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="],
@@ -743,12 +774,16 @@
"rehype-katex": ["rehype-katex@7.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/katex": "^0.16.0", "hast-util-from-html-isomorphic": "^2.0.0", "hast-util-to-text": "^4.0.0", "katex": "^0.16.0", "unist-util-visit-parents": "^6.0.0", "vfile": "^6.0.0" } }, "sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA=="],
+ "remark-gfm": ["remark-gfm@4.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="],
+
"remark-math": ["remark-math@6.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-math": "^3.0.0", "micromark-extension-math": "^3.0.0", "unified": "^11.0.0" } }, "sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA=="],
"remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="],
"remark-rehype": ["remark-rehype@11.1.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "mdast-util-to-hast": "^13.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ=="],
+ "remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="],
+
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
@@ -873,6 +908,8 @@
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
+ "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
+
"parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
diff --git a/services/frontend/package.json b/services/frontend/package.json
index 27b9a85..98bd7bf 100644
--- a/services/frontend/package.json
+++ b/services/frontend/package.json
@@ -36,6 +36,7 @@
"react-router": "^7.2.0",
"react-router-dom": "^7.2.0",
"rehype-katex": "^7.0.1",
+ "remark-gfm": "^4.0.1",
"remark-math": "^6.0.0",
"tailwind-merge": "^3.0.2",
"tailwindcss": "^4.0.9",
diff --git a/services/frontend/src/pages/Competition/index.tsx b/services/frontend/src/pages/Competition/index.tsx
index e472ddb..ed1320c 100644
--- a/services/frontend/src/pages/Competition/index.tsx
+++ b/services/frontend/src/pages/Competition/index.tsx
@@ -1,12 +1,15 @@
import { useParams, Link, useNavigate } from "react-router-dom";
import { Button } from "@/components/ui/button";
-import { ArrowLeft, Clock, Trophy, BookOpen } from "lucide-react";
+import { ArrowLeft, Clock, Trophy, BookOpen, BarChart2, AlertCircle } from "lucide-react";
import ReactMarkdown from "react-markdown";
import { useQuery, useMutation } from "@tanstack/react-query";
import { getCompetition, startCompetition } from "@/shared/api/competitions";
import { getCompetitionTasks } from "@/shared/api/session";
import { Loading } from "@/components/ui/loading";
import { CompetitionType } from "@/shared/types/competition";
+import remarkMath from "remark-math";
+import remarkGfm from "remark-gfm";
+import rehypeKatex from "rehype-katex";
const CompetitionPage = () => {
const { id } = useParams<{ id: string }>();
@@ -39,6 +42,7 @@ const CompetitionPage = () => {
console.error("Failed to start competition:", error);
}
});
+
const formatDate = (date?: Date | string) => {
if (!date) return "";
@@ -56,6 +60,10 @@ const CompetitionPage = () => {
const handleStart = () => {
startMutation.mutate();
};
+
+ const handleViewResults = () => {
+ console.log("sorryan");
+ };
if (competitionQuery.isLoading) {
return