├── .devcontainer ├── .gitignore ├── Dockerfile ├── devcontainer.json ├── docker-compose.yml └── postCreateCommand.sh ├── .dockerignore ├── .editorconfig ├── .github └── workflows │ ├── build.yml │ ├── test-backend.yml │ └── test-frontend.yml ├── .gitignore ├── .gitpod.docker-compose.yml ├── .gitpod.yml ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── Dockerfile ├── LICENSE ├── README.md ├── backend ├── .coveragerc ├── .flake8 ├── account │ ├── __init__.py │ ├── fixtures │ │ └── data.yaml │ ├── middleware.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto_20170209_1028.py │ │ ├── 0003_userprofile_total_score.py │ │ ├── 0005_auto_20170830_1154.py │ │ ├── 0006_user_session_keys.py │ │ ├── 0008_auto_20171011_1214.py │ │ ├── 0009_auto_20171125_1514.py │ │ ├── 0010_auto_20180501_0436.py │ │ ├── 0011_auto_20180501_0456.py │ │ ├── 0012_userprofile_language.py │ │ ├── 0013_auto_20210117_1246.py │ │ ├── 0014_auto_20210126_1738.py │ │ ├── 0015_auto_20210703_1545.py │ │ ├── 0016_auto_20210703_1559.py │ │ ├── 0017_auto_20210703_1612.py │ │ ├── 0018_auto_20211030_1535.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tasks.py │ ├── templates │ │ ├── email_auth.html │ │ └── reset_password_email.html │ ├── tests.py │ ├── urls │ │ ├── __init__.py │ │ ├── admin.py │ │ └── oj.py │ └── views │ │ ├── __init__.py │ │ ├── admin.py │ │ └── oj.py ├── announcement │ ├── __init__.py │ ├── fixtures │ │ └── data.yaml │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto_20171011_1214.py │ │ ├── 0003_auto_20180501_0436.py │ │ ├── 0004_alter_announcement_id.py │ │ ├── 0005_announcement_top_fixed.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls │ │ ├── __init__.py │ │ ├── admin.py │ │ └── oj.py │ └── views │ │ ├── __init__.py │ │ ├── admin.py │ │ └── oj.py ├── assignment │ ├── __init__.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_alter_assignment_id.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls │ │ ├── professor.py │ │ └── student.py │ └── views │ │ ├── professor.py │ │ └── student.py ├── banner │ ├── __init__.py │ ├── fixtures │ │ └── data.yaml │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls │ │ ├── __init__.py │ │ ├── admin.py │ │ └── oj.py │ └── views │ │ ├── __init__.py │ │ ├── admin.py │ │ └── oj.py ├── conf │ ├── __init__.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto_20171011_1214.py │ │ ├── 0003_judgeserver_is_disabled.py │ │ ├── 0004_auto_20180501_0436.py │ │ ├── 0005_alter_judgeserver_id.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── oj.py │ │ └── professor.py │ └── views.py ├── contest │ ├── __init__.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto_20170209_0845.py │ │ ├── 0003_auto_20170217_0820.py │ │ ├── 0004_auto_20170717_1324.py │ │ ├── 0005_auto_20170823_0918.py │ │ ├── 0006_auto_20171011_1214.py │ │ ├── 0007_contestannouncement_visible.py │ │ ├── 0008_contest_allowed_ip_ranges.py │ │ ├── 0009_auto_20180501_0436.py │ │ ├── 0010_auto_20190326_0201.py │ │ ├── 0011_auto_20211030_1535.py │ │ ├── 0012_rename_total_score_acmcontestrank_total_penalty.py │ │ ├── 0013_contestannouncement_problem.py │ │ ├── 0014_auto_20220211_1432.py │ │ ├── 0015_auto_20220211_1433.py │ │ ├── 0016_rename_prize_contest_prizes.py │ │ ├── 0017_alter_contest_allowed_groups.py │ │ ├── 0018_auto_20220225_1422.py │ │ ├── 0019_alter_contestprize_contest.py │ │ ├── 0020_auto_20220315_1654.py │ │ ├── 0021_contest_rank_penalty_visible.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls │ │ ├── __init__.py │ │ ├── admin.py │ │ └── oj.py │ └── views │ │ ├── __init__.py │ │ ├── admin.py │ │ └── oj.py ├── course │ ├── __init__.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto_20210808_1709.py │ │ ├── 0003_auto_20211226_1458.py │ │ ├── 0004_registration_bookmark.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls │ │ ├── __init__.py │ │ ├── professor.py │ │ └── student.py │ └── views │ │ ├── __init__.py │ │ ├── professor.py │ │ └── student.py ├── data │ ├── .gitignore │ ├── config │ │ └── .gitkeep │ ├── log │ │ └── .gitkeep │ ├── public │ │ ├── avatar │ │ │ └── default.png │ │ ├── upload │ │ │ ├── banner1.png │ │ │ ├── banner2.png │ │ │ ├── banner3.png │ │ │ └── banner4.png │ │ └── website │ │ │ └── favicon.ico │ ├── ssl │ │ └── .gitkeep │ └── test_case │ │ └── .gitkeep ├── deploy │ ├── entrypoint.sh │ ├── health_check.py │ ├── nginx │ │ ├── api_proxy.conf │ │ ├── locations.conf │ │ └── nginx.conf │ ├── requirements.txt │ ├── supervisord.conf │ └── test_case_rsync │ │ ├── Dockerfile │ │ ├── docker-compose.yml │ │ ├── rsyncd.conf │ │ └── run.sh ├── docs │ └── data.json ├── group │ ├── __init__.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_alter_groupmember_is_admin.py │ │ ├── 0003_auto_20220203_2234.py │ │ ├── 0004_rename_is_admin_groupmember_is_group_admin.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls │ │ ├── __init__.py │ │ ├── admin.py │ │ └── oj.py │ └── views │ │ ├── __init__.py │ │ ├── admin.py │ │ └── oj.py ├── judge │ ├── __init__.py │ ├── dispatcher.py │ ├── languages.py │ └── tasks.py ├── manage.py ├── oj │ ├── __init__.py │ ├── asgi.py │ ├── dev_settings.py │ ├── production_settings.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── options │ ├── __init__.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto_20180501_0436.py │ │ ├── 0003_migrate_languages_options.py │ │ ├── 0004_auto_20211030_1535.py │ │ └── __init__.py │ ├── models.py │ ├── options.py │ ├── tests.py │ └── views.py ├── problem │ ├── __init__.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_problem__id.py │ │ ├── 0003_auto_20170217_0820.py │ │ ├── 0004_auto_20170501_0637.py │ │ ├── 0005_auto_20170815_1258.py │ │ ├── 0006_auto_20170823_0918.py │ │ ├── 0008_auto_20170923_1318.py │ │ ├── 0009_auto_20171011_1214.py │ │ ├── 0010_problem_spj_compile_ok.py │ │ ├── 0011_fix_problem_ac_count.py │ │ ├── 0012_auto_20180501_0436.py │ │ ├── 0013_problem_io_mode.py │ │ ├── 0014_problem_share_submission.py │ │ ├── 0015_auto_20210730_2244.py │ │ ├── 0016_auto_20210801_2045.py │ │ ├── 0017_auto_20210814_1415.py │ │ ├── 0018_auto_20211226_1458.py │ │ ├── 0019_problem_bank.py │ │ ├── 0020_auto_20220327_2013.py │ │ ├── 0021_auto_20220329_1451.py │ │ ├── 0022_alter_problem_problem_set.py │ │ ├── 0023_auto_20220329_1517.py │ │ ├── 0024_alter_problemset_problems.py │ │ ├── 0025_alter_problemset_problem_set_group.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── oj.py │ │ ├── professor.py │ │ └── student.py │ ├── utils.py │ └── views │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── oj.py │ │ ├── professor.py │ │ └── student.py ├── qna │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_question_content_question_course_id_and_more.py │ │ ├── 0003_alter_question_content_alter_question_course_id_and_more.py │ │ ├── 0004_rename_course_id_question_course_and_more.py │ │ ├── 0005_rename_status_question_is_open.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls │ │ ├── professor.py │ │ └── student.py │ └── views │ │ ├── professor.py │ │ └── student.py ├── run_test.py ├── submission │ ├── __init__.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto_20170509_1203.py │ │ ├── 0005_submission_username.py │ │ ├── 0006_auto_20170830_1154.py │ │ ├── 0007_auto_20170923_1318.py │ │ ├── 0008_submission_ip.py │ │ ├── 0009_delete_user_output.py │ │ ├── 0011_fix_submission_number.py │ │ ├── 0012_auto_20180501_0436.py │ │ ├── 0013_auto_20210730_2244.py │ │ ├── 0014_auto_20210802_2115.py │ │ ├── 0015_remove_submission_score.py │ │ ├── 0016_auto_20211226_1458.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls.py │ └── views.py └── utils │ ├── __init__.py │ ├── api │ ├── __init__.py │ ├── _serializers.py │ ├── api.py │ └── tests.py │ ├── cache.py │ ├── captcha │ ├── Menlo.ttc │ ├── __init__.py │ ├── timesbi.ttf │ └── views.py │ ├── constants.py │ ├── decorators.py │ ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── inituser.py │ ├── migrate_data.py │ ├── models.py │ ├── serializers.py │ ├── shortcuts.py │ ├── tasks.py │ ├── throttling.py │ ├── urls.py │ ├── views.py │ └── xss_filter.py ├── db_backup.sh ├── docker-compose.yml └── frontend ├── .browserslistrc ├── .editorconfig ├── .eslintrc.js ├── babel.config.js ├── cypress.json ├── cypress ├── fixtures │ ├── example.json │ └── problem-example.json ├── integration │ ├── admin │ │ └── problem │ │ │ └── admin_problem.spec.js │ └── oj │ │ └── problem │ │ └── problem.spec.js ├── plugins │ └── index.js └── support │ ├── commands.js │ └── index.js ├── package.json ├── postcss.config.js ├── public ├── cache.dll.json ├── dll │ ├── dll.c9dc8146.dll.js │ └── dll.manifest.json └── loader.css ├── src ├── assets │ ├── Cup.png │ ├── icons │ │ ├── github.svg │ │ ├── kakao.svg │ │ ├── link.svg │ │ └── mail.svg │ ├── logos │ │ ├── codingPlatformLogo.png │ │ ├── logo.png │ │ ├── logo.svg │ │ ├── signature.png │ │ └── signature.svg │ └── standing.png ├── fonts │ ├── Manrope-Bold.ttf │ ├── Manrope-ExtraBold.ttf │ ├── Manrope-ExtraLight.ttf │ ├── Manrope-Light.ttf │ ├── Manrope-Medium.ttf │ ├── Manrope-Regular.ttf │ └── Manrope-SemiBold.ttf ├── pages │ ├── admin │ │ ├── App.vue │ │ ├── api.js │ │ ├── components │ │ │ ├── Accordion.vue │ │ │ ├── CodeMirror.vue │ │ │ ├── KatexEditor.vue │ │ │ ├── ManageProblemModal.vue │ │ │ ├── Panel.vue │ │ │ ├── ProblemSetGroupModal.vue │ │ │ ├── ProblemSetModal.vue │ │ │ ├── SideMenu.vue │ │ │ ├── Tiptap.vue │ │ │ ├── btn │ │ │ │ ├── Cancel.vue │ │ │ │ ├── IconBtn.vue │ │ │ │ └── Save.vue │ │ │ └── infoCard.vue │ │ ├── index.html │ │ ├── index.js │ │ ├── router.js │ │ └── views │ │ │ ├── Home.vue │ │ │ ├── contest │ │ │ ├── Contest.vue │ │ │ └── ContestList.vue │ │ │ ├── general │ │ │ ├── Announcement.vue │ │ │ ├── Banner.vue │ │ │ ├── Conf.vue │ │ │ ├── Dashboard.vue │ │ │ ├── JudgeServer.vue │ │ │ ├── Login.vue │ │ │ ├── PruneTestCase.vue │ │ │ └── User.vue │ │ │ ├── index.js │ │ │ └── problem │ │ │ ├── AddPublicProblem.vue │ │ │ ├── Problem.vue │ │ │ ├── ProblemList.vue │ │ │ └── ProblemSet.vue │ ├── oj │ │ ├── App.vue │ │ ├── api.js │ │ ├── components │ │ │ ├── Banner.vue │ │ │ ├── CodeMirror.vue │ │ │ ├── ColorRoundButton.vue │ │ │ ├── ContestInformation.vue │ │ │ ├── Dropdown.vue │ │ │ ├── Footer.vue │ │ │ ├── Header.vue │ │ │ ├── Highlight.vue │ │ │ ├── Modal.vue │ │ │ ├── NeonBox.vue │ │ │ ├── PageTitle.vue │ │ │ ├── PageTop.vue │ │ │ ├── ProblemSetGroup.vue │ │ │ ├── ShadowRoundButton.vue │ │ │ ├── Sidemenu.vue │ │ │ ├── StatusBadge.vue │ │ │ ├── Table.vue │ │ │ └── mixins │ │ │ │ ├── emitter.js │ │ │ │ ├── form.js │ │ │ │ ├── index.js │ │ │ │ └── problem.js │ │ ├── index.html │ │ ├── index.js │ │ ├── router │ │ │ ├── index.js │ │ │ └── routes.js │ │ └── views │ │ │ ├── announcement │ │ │ ├── Announcement.vue │ │ │ └── AnnouncementList.vue │ │ │ ├── contest │ │ │ ├── ContestClarification.vue │ │ │ ├── ContestDetail.vue │ │ │ ├── ContestList.vue │ │ │ ├── ContestProblemList.vue │ │ │ └── ContestRanking.vue │ │ │ ├── general │ │ │ ├── 404.vue │ │ │ └── Home.vue │ │ │ ├── index.js │ │ │ ├── lecture │ │ │ ├── LectureAssignmentDetail.vue │ │ │ ├── LectureAssignmentList.vue │ │ │ ├── LectureDashboard.vue │ │ │ ├── LectureList.vue │ │ │ ├── LectureQnA.vue │ │ │ └── LectureQnADetail.vue │ │ │ ├── problem │ │ │ ├── Problem.vue │ │ │ ├── ProblemList.vue │ │ │ ├── ProblemSet.vue │ │ │ └── ProblemSidebar.vue │ │ │ └── user │ │ │ ├── ApplyResetPassword.vue │ │ │ ├── DeleteAccount.vue │ │ │ ├── EmailAuth.vue │ │ │ ├── Login.vue │ │ │ ├── Logout.vue │ │ │ ├── ProfileSetting.vue │ │ │ ├── Register.vue │ │ │ ├── ResetPassword.vue │ │ │ └── SendEmailAuth.vue │ └── professor │ │ ├── App.vue │ │ ├── api.js │ │ ├── components │ │ ├── Accordion.vue │ │ ├── CodeMirror.vue │ │ ├── KatexEditor.vue │ │ ├── Panel.vue │ │ ├── SideMenu.vue │ │ ├── Tiptap.vue │ │ ├── btn │ │ │ ├── Cancel.vue │ │ │ ├── IconBtn.vue │ │ │ └── Save.vue │ │ └── infoCard.vue │ │ ├── index.html │ │ ├── index.js │ │ ├── router.js │ │ └── views │ │ ├── Home.vue │ │ ├── assignment │ │ ├── AssignmentList.vue │ │ ├── CreateAssignment.vue │ │ └── ImportPublicProblem.vue │ │ ├── general │ │ ├── CourseBookmark.vue │ │ ├── CourseDashboard.vue │ │ ├── CourseModal.vue │ │ ├── Dashboard.vue │ │ └── Login.vue │ │ ├── index.js │ │ ├── problem │ │ ├── Problem.vue │ │ ├── ProblemGrade.vue │ │ └── SubmissionDetail.vue │ │ ├── qna │ │ └── QnA.vue │ │ └── users │ │ ├── RegisterNewUser.vue │ │ └── UserList.vue ├── plugins │ └── highlight.js ├── store │ ├── index.js │ ├── modules │ │ ├── contest.js │ │ ├── course.js │ │ ├── group.js │ │ └── user.js │ └── types.js ├── styles │ ├── bootstrap.scss │ ├── common.scss │ ├── markdown.scss │ ├── tailwind.css │ └── tiptapview.scss └── utils │ ├── constants.js │ ├── filters.js │ ├── storage.js │ ├── time.js │ └── utils.js ├── tailwind.config.js ├── vue.config.js └── yarn.lock /.devcontainer/.gitignore: -------------------------------------------------------------------------------- 1 | judge-server 2 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # [Choice] Python version (https://github.com/microsoft/vscode-dev-containers/tree/main/containers/python-3) 2 | ARG VARIANT=3.8-bullseye 3 | FROM mcr.microsoft.com/vscode/devcontainers/python:${VARIANT} 4 | 5 | # [Choice] Node.js version: none, lts/*, 16, 14, 12, 10 6 | ARG NODE_VERSION="16" 7 | RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi 8 | 9 | RUN apt-get update && \ 10 | export DEBIAN_FRONTEND=noninteractive && \ 11 | apt-get -y install --no-install-recommends \ 12 | libgtk2.0-0 \ 13 | libgtk-3-0 \ 14 | libgbm-dev \ 15 | libnotify-dev \ 16 | libgconf-2-4 \ 17 | libnss3 \ 18 | libxss1 \ 19 | libasound2 \ 20 | libxtst6 xauth xvfb 21 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Coding Platform", 3 | "dockerComposeFile": "docker-compose.yml", 4 | "service": "app", 5 | "workspaceFolder": "/workspace", 6 | "forwardPorts": [10000], 7 | 8 | "postCreateCommand": "./.devcontainer/postCreateCommand.sh", 9 | 10 | "extensions": [ 11 | "bradlc.vscode-tailwindcss", 12 | "dbaeumer.vscode-eslint", 13 | "donjayamanne.githistory", 14 | "eamodio.gitlens", 15 | "editorconfig.editorconfig", 16 | "gruntfuggly.todo-tree", 17 | "johnsoncodehk.volar", 18 | "mhutchie.git-graph", 19 | "ms-python.python", 20 | "naumovs.color-highlight", 21 | "oderwat.indent-rainbow", 22 | "pkief.material-icon-theme", 23 | "rangav.vscode-thunder-client" 24 | ], 25 | 26 | // Connect as non-root user (https://code.visualstudio.com/remote/advancedcontainers/add-nonroot-user) 27 | "remoteUser": "vscode" 28 | } 29 | -------------------------------------------------------------------------------- /.devcontainer/postCreateCommand.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | set -x 3 | 4 | # Install Python packages 5 | python3 -m pip install wheel 6 | python3 -m pip install -r /workspace/backend/deploy/requirements.txt 7 | 8 | # Django Setup 9 | echo `cat /dev/urandom | head -1 | md5sum | head -c 32` > /workspace/backend/data/config/secret.key 10 | python3 /workspace/backend/manage.py migrate 11 | python3 /workspace/backend/manage.py inituser --username root --password rootroot --action create_super_admin 12 | 13 | # Add `judge-server-dev` container to database 14 | echo " 15 | from conf.models import JudgeServer 16 | from django.utils import timezone 17 | 18 | JudgeServer.objects.create( 19 | hostname='hostname', 20 | judger_version='version', 21 | cpu_core=1, 22 | memory_usage=0, 23 | cpu_usage=0, 24 | ip='127.0.0.1', 25 | service_url='$JUDGE_SERVER_URL', 26 | last_heartbeat=timezone.now() 27 | ) 28 | " | python3 /workspace/backend/manage.py shell 29 | 30 | # Register judge server token 31 | echo " 32 | from options.options import SysOptions 33 | SysOptions.judge_server_token='$JUDGE_SERVER_TOKEN' 34 | " | python3 /workspace/backend/manage.py shell 35 | 36 | # Install Node packages 37 | yarn --cwd /workspace/frontend install 38 | yarn --cwd /workspace/frontend cypress install 39 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # Container Volume Mount 2 | /data 3 | 4 | # Python Virtual Environment 5 | **/.env 6 | **/.venv 7 | **/env/ 8 | **/venv/ 9 | **/ENV/ 10 | **/env.bak/ 11 | **/venv.bak/ 12 | **/pythonenv* 13 | 14 | # IDE Setting 15 | **/.idea 16 | **/.vscode 17 | 18 | # Git 19 | .git 20 | 21 | # Mac OS 22 | **/.DS_Store 23 | 24 | # Node Packages 25 | **/node_modules 26 | 27 | # Vue CLI DLL Plugin 28 | frontend/public 29 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_style = space 3 | end_of_line = lf 4 | insert_final_newline = true 5 | trim_trailing_whitespace = true 6 | 7 | [*.{html,css,js,json,vue,yml}] 8 | indent_size = 2 9 | 10 | [*.{py,sh,conf}] 11 | indent_size = 4 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build Container Image 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout to repository 14 | uses: actions/checkout@v2 15 | 16 | - name: Set up Docker Buildx 17 | uses: docker/setup-buildx-action@v1 18 | 19 | - name: Login to GitHub Container Registry 20 | uses: docker/login-action@v1 21 | with: 22 | registry: ghcr.io 23 | username: ${{ github.actor }} 24 | password: ${{ secrets.GITHUB_TOKEN }} 25 | 26 | - name: Build and push container image 27 | uses: docker/build-push-action@v2 28 | with: 29 | push: true 30 | tags: ghcr.io/skkuding/coding-platform:latest 31 | -------------------------------------------------------------------------------- /.github/workflows/test-frontend.yml: -------------------------------------------------------------------------------- 1 | name: Frontend Tests 2 | 3 | # Controls when the action will run. 4 | on: 5 | # Triggers the workflow on push or pull request events but only for the master branch 6 | pull_request: 7 | paths: 8 | - "frontend/**" 9 | - ".github/workflows/test-frontend.yml" 10 | 11 | # Allows you to run this workflow manually from the Actions tab 12 | workflow_dispatch: 13 | 14 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 15 | jobs: 16 | test: 17 | runs-on: ubuntu-18.04 18 | env: 19 | working-directory: ./frontend 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | 24 | - uses: actions/setup-node@v2 25 | with: 26 | node-version: "14" 27 | 28 | - name: Cache Node modules 29 | id: cache-yarn 30 | uses: actions/cache@v2 31 | with: 32 | path: | 33 | ./frontend/node_modules 34 | ./frontend/public 35 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 36 | restore-keys: | 37 | ${{ runner.os }}-yarn- 38 | 39 | - name: Install Node packages 40 | if: steps.cache-yarn.outputs.cache-hit != 'true' 41 | working-directory: ${{ env.working-directory }} 42 | run: yarn install 43 | 44 | - name: Check styles 45 | working-directory: ${{ env.working-directory }} 46 | run: yarn lint --no-fix 47 | 48 | - name: Build production files 49 | working-directory: ${{ env.working-directory }} 50 | run: yarn build 51 | -------------------------------------------------------------------------------- /.gitpod.docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | db: 5 | image: postgres:12-alpine 6 | restart: unless-stopped 7 | volumes: 8 | - postgres-data:/var/lib/postgresql/data 9 | environment: 10 | - POSTGRES_PASSWORD=codingplatform 11 | - POSTGRES_USER=codingplatform 12 | - POSTGRES_DB=codingplatform 13 | ports: 14 | - "0.0.0.0:5432:5432" 15 | 16 | redis: 17 | image: redis:4.0-alpine 18 | restart: unless-stopped 19 | volumes: 20 | - redis-data:/data 21 | ports: 22 | - "0.0.0.0:6379:6379" 23 | 24 | volumes: 25 | postgres-data: null 26 | redis-data: null 27 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-vscode-remote.remote-containers" 4 | ], 5 | "unwantedRecommendations": [ 6 | "esbenp.prettier-vscode" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Vue.js: Chrome", 6 | "type": "chrome", 7 | "request": "launch", 8 | "url": "http://localhost:8080", 9 | "webRoot": "${workspaceFolder}/frontend/src", 10 | "breakOnLoad": true, 11 | "sourceMapPathOverrides": { 12 | "webpack:///src/*": "${webRoot}/*" 13 | } 14 | }, 15 | { 16 | "name": "Vue.js: Edge", 17 | "type": "msedge", 18 | "request": "launch", 19 | "url": "http://localhost:8080", 20 | "webRoot": "${workspaceFolder}/frontend/src", 21 | "breakOnLoad": true, 22 | "sourceMapPathOverrides": { 23 | "webpack:///src/*": "${webRoot}/*" 24 | } 25 | }, 26 | { 27 | "name": "Python: Django", 28 | "type": "python", 29 | "request": "launch", 30 | "program": "${workspaceFolder}/backend/manage.py", 31 | "args": [ 32 | "runserver", 33 | "--noreload" 34 | ], 35 | "django": true 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.bracketPairColorization.enabled": true, 3 | "editor.codeActionsOnSave": { 4 | "source.fixAll.eslint": true 5 | }, 6 | "editor.formatOnSave": true, 7 | "eslint.workingDirectories": [ 8 | "frontend/" 9 | ], 10 | "files.autoSave": "off", 11 | "material-icon-theme.activeIconPack": "vue_vuex", 12 | "prettier.enable": false, 13 | "python.formatting.provider": "none", 14 | "python.linting.flake8Args": [ 15 | "--config", 16 | "backend/.flake8" 17 | ], 18 | "python.linting.flake8Enabled": true, 19 | "[javascript][typescript][vue]": { 20 | "editor.formatOnSave": false 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Build Stage 2 | FROM node:14-alpine AS builder 3 | 4 | ADD ./frontend /build 5 | WORKDIR /build 6 | 7 | RUN yarn install && \ 8 | yarn build 9 | 10 | # Deploy Stage 11 | FROM python:3.8.12-alpine3.15 12 | 13 | ENV OJ_ENV production 14 | ENV NODE_ENV production 15 | 16 | ADD ./backend /app 17 | WORKDIR /app 18 | 19 | HEALTHCHECK --interval=5s --retries=3 CMD python /app/deploy/health_check.py 20 | 21 | RUN apk add --update --no-cache build-base nginx openssl curl unzip supervisor jpeg-dev zlib-dev postgresql-dev freetype-dev && \ 22 | pip install --no-cache-dir -r /app/deploy/requirements.txt && \ 23 | apk del build-base --purge 24 | 25 | COPY --from=builder /build/dist /app/dist 26 | 27 | ENTRYPOINT /app/deploy/entrypoint.sh 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-present skkuding 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /backend/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = */urls/* 3 | */__init__.py 4 | */tests.py 5 | */migrations/* 6 | *urls.py 7 | utils/xss_filter.py 8 | -------------------------------------------------------------------------------- /backend/.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | exclude = 3 | xss_filter.py, 4 | */migrations/, 5 | *settings.py, 6 | */apps.py, 7 | .venv/, 8 | venv/ 9 | max-line-length = 180 10 | inline-quotes = " 11 | no-accept-encodings = True 12 | -------------------------------------------------------------------------------- /backend/account/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/account/__init__.py -------------------------------------------------------------------------------- /backend/account/middleware.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.db import connection 3 | from django.utils.timezone import now 4 | from django.utils.deprecation import MiddlewareMixin 5 | 6 | from utils.api import JSONResponse 7 | 8 | 9 | class SessionRecordMiddleware(MiddlewareMixin): 10 | def process_request(self, request): 11 | request.ip = request.META.get(settings.IP_HEADER, request.META.get("REMOTE_ADDR")) 12 | if request.user.is_authenticated: 13 | session = request.session 14 | session["user_agent"] = request.META.get("HTTP_USER_AGENT", "") 15 | session["ip"] = request.ip 16 | session["last_activity"] = now() 17 | user_sessions = request.user.session_keys 18 | if session.session_key not in user_sessions: 19 | user_sessions.append(session.session_key) 20 | request.user.save() 21 | 22 | 23 | class AdminRoleRequiredMiddleware(MiddlewareMixin): 24 | def process_request(self, request): 25 | path = request.path_info 26 | if path.startswith("/admin/") or path.startswith("/api/admin/"): 27 | if not (request.user.is_authenticated and request.user.is_admin_role()): 28 | return JSONResponse.response({"error": "login-required", "data": "Please login in first"}) 29 | 30 | 31 | class LogSqlMiddleware(MiddlewareMixin): 32 | def process_response(self, request, response): 33 | print("\033[94m", "#" * 30, "\033[0m") 34 | time_threshold = 0.03 35 | for query in connection.queries: 36 | if float(query["time"]) > time_threshold: 37 | print("\033[93m", query, "\n", "-" * 30, "\033[0m") 38 | else: 39 | print(query, "\n", "-" * 30) 40 | return response 41 | -------------------------------------------------------------------------------- /backend/account/migrations/0002_auto_20170209_1028.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.12 on 2017-02-09 10:28 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('account', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='user', 17 | name='problem_permission', 18 | field=models.CharField(default='None', max_length=24), 19 | ), 20 | migrations.AlterField( 21 | model_name='user', 22 | name='admin_type', 23 | field=models.CharField(default='Regular User', max_length=24), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /backend/account/migrations/0003_userprofile_total_score.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2017-08-20 02:03 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('account', '0002_auto_20170209_1028'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='userprofile', 17 | name='total_score', 18 | field=models.BigIntegerField(default=0), 19 | ), 20 | migrations.RenameField( 21 | model_name='userprofile', 22 | old_name='accepted_problem_number', 23 | new_name='accepted_number', 24 | ), 25 | migrations.RemoveField( 26 | model_name='userprofile', 27 | name='time_zone', 28 | ) 29 | ] 30 | -------------------------------------------------------------------------------- /backend/account/migrations/0005_auto_20170830_1154.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-08-30 11:54 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import jsonfield.fields 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('account', '0003_userprofile_total_score'), 13 | ] 14 | 15 | operations = [ 16 | migrations.RenameField( 17 | model_name='userprofile', 18 | old_name='problems_status', 19 | new_name='acm_problems_status', 20 | ), 21 | migrations.AddField( 22 | model_name='userprofile', 23 | name='oi_problems_status', 24 | field=jsonfield.fields.JSONField(default={}), 25 | ), 26 | migrations.RemoveField( 27 | model_name='user', 28 | name='real_name', 29 | ), 30 | migrations.RemoveField( 31 | model_name='userprofile', 32 | name='student_id', 33 | ), 34 | migrations.AddField( 35 | model_name='userprofile', 36 | name='real_name', 37 | field=models.CharField(max_length=30, blank=True, null=True), 38 | ), 39 | ] 40 | -------------------------------------------------------------------------------- /backend/account/migrations/0006_user_session_keys.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-09-16 06:22 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import jsonfield.fields 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('account', '0005_auto_20170830_1154'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name='user', 18 | name='session_keys', 19 | field=jsonfield.fields.JSONField(default=[]), 20 | ), 21 | migrations.RenameField( 22 | model_name='userprofile', 23 | old_name='phone_number', 24 | new_name='github', 25 | ), 26 | migrations.AlterField( 27 | model_name='userprofile', 28 | name='avatar', 29 | field=models.CharField(default='/static/avatar/default.png', max_length=50), 30 | ), 31 | migrations.AlterField( 32 | model_name='userprofile', 33 | name='github', 34 | field=models.CharField(blank=True, max_length=50, null=True), 35 | ), 36 | ] 37 | -------------------------------------------------------------------------------- /backend/account/migrations/0009_auto_20171125_1514.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-11-25 15:14 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('account', '0008_auto_20171011_1214'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='userprofile', 17 | name='avatar', 18 | field=models.CharField(default='/public/avatar/default.png', max_length=256), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /backend/account/migrations/0011_auto_20180501_0456.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.3 on 2018-05-01 04:56 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('account', '0010_auto_20180501_0436'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='user', 17 | name='email', 18 | field=models.TextField(null=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /backend/account/migrations/0012_userprofile_language.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2018-07-15 02:06 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('account', '0011_auto_20180501_0456'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='userprofile', 17 | name='language', 18 | field=models.TextField(null=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /backend/account/migrations/0013_auto_20210117_1246.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.7 on 2021-01-17 12:46 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('account', '0012_userprofile_language'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='userprofile', 15 | name='major', 16 | ), 17 | migrations.RemoveField( 18 | model_name='userprofile', 19 | name='school', 20 | ), 21 | migrations.AddField( 22 | model_name='user', 23 | name='major', 24 | field=models.TextField(null=True), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /backend/account/migrations/0014_auto_20210126_1738.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.7 on 2021-01-26 17:38 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('account', '0013_auto_20210117_1246'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='user', 15 | name='email_auth_token', 16 | field=models.TextField(null=True), 17 | ), 18 | migrations.AddField( 19 | model_name='user', 20 | name='has_email_auth', 21 | field=models.BooleanField(default=True), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /backend/account/migrations/0015_auto_20210703_1545.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.24 on 2021-07-03 06:45 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('account', '0014_auto_20210126_1738'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='user', 15 | name='auth_token', 16 | ), 17 | migrations.RemoveField( 18 | model_name='user', 19 | name='tfa_token', 20 | ), 21 | migrations.RemoveField( 22 | model_name='user', 23 | name='two_factor_auth', 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /backend/account/migrations/0016_auto_20210703_1559.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.24 on 2021-07-03 06:59 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('account', '0015_auto_20210703_1545'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='user', 15 | name='open_api', 16 | ), 17 | migrations.RemoveField( 18 | model_name='user', 19 | name='open_api_appkey', 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /backend/account/migrations/0017_auto_20210703_1612.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.24 on 2021-07-03 07:12 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('account', '0016_auto_20210703_1559'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='userprofile', 15 | name='blog', 16 | ), 17 | migrations.RemoveField( 18 | model_name='userprofile', 19 | name='github', 20 | ), 21 | migrations.RemoveField( 22 | model_name='userprofile', 23 | name='mood', 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /backend/account/migrations/0018_auto_20211030_1535.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.4 on 2021-10-30 06:35 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('account', '0017_auto_20210703_1612'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='user', 15 | name='id', 16 | field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), 17 | ), 18 | migrations.AlterField( 19 | model_name='user', 20 | name='session_keys', 21 | field=models.JSONField(default=list), 22 | ), 23 | migrations.AlterField( 24 | model_name='userprofile', 25 | name='acm_problems_status', 26 | field=models.JSONField(default=dict), 27 | ), 28 | migrations.AlterField( 29 | model_name='userprofile', 30 | name='id', 31 | field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), 32 | ), 33 | migrations.AlterField( 34 | model_name='userprofile', 35 | name='oi_problems_status', 36 | field=models.JSONField(default=dict), 37 | ), 38 | ] 39 | -------------------------------------------------------------------------------- /backend/account/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/account/migrations/__init__.py -------------------------------------------------------------------------------- /backend/account/tasks.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import dramatiq 3 | 4 | from options.options import SysOptions 5 | from utils.shortcuts import send_email, DRAMATIQ_WORKER_ARGS 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | 10 | @dramatiq.actor(**DRAMATIQ_WORKER_ARGS(max_retries=3)) 11 | def send_email_async(from_name, to_email, to_name, subject, content): 12 | if not SysOptions.smtp_config: 13 | return 14 | try: 15 | send_email(smtp_config=SysOptions.smtp_config, 16 | from_name=from_name, 17 | to_email=to_email, 18 | to_name=to_name, 19 | subject=subject, 20 | content=content) 21 | except Exception as e: 22 | logger.exception(e) 23 | -------------------------------------------------------------------------------- /backend/account/templates/email_auth.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 7 | 8 | 9 | 27 | 28 |
6 | {{ website_name }}
10 |
11 |
12 | Register Authentication 13 |
14 |
15 | Click below button to complete register 16 |
17 |

18 | 19 | Authenticate 20 | 21 |

22 |

23 | If you still have any questions, please contact here. 24 |

25 |
26 |
-------------------------------------------------------------------------------- /backend/account/templates/reset_password_email.html: -------------------------------------------------------------------------------- 1 |
2 | 4 | 5 | 6 | 9 | 10 | 11 | 27 | 28 | 29 | 30 |
8 | {{ website_name }}
12 |
13 |

Hello, {{ username }}:

14 |

15 | Please click {{ link }} to reset your password in 20 minutes. 16 |

17 |

18 | To protect your account, please do not use simple passwords. 19 |

20 |

21 | If you still have any questions, please contact here. 22 |

23 |

24 |

{{ website_name }}

25 |
26 |
31 |
-------------------------------------------------------------------------------- /backend/account/urls/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/account/urls/__init__.py -------------------------------------------------------------------------------- /backend/account/urls/admin.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from ..views.admin import UserAdminAPI, GenerateUserAPI 4 | 5 | urlpatterns = [ 6 | path("user/", UserAdminAPI.as_view(), name="user_admin_api"), 7 | path("generate_user/", GenerateUserAPI.as_view(), name="generate_user_api"), 8 | ] 9 | -------------------------------------------------------------------------------- /backend/account/urls/oj.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from ..views.oj import (ApplyResetPasswordAPI, DeleteAccountAPI, ResetPasswordAPI, 4 | UserChangePasswordAPI, UserRegisterAPI, EmailAuthAPI, UserChangeEmailAPI, 5 | UserLoginAPI, UserLogoutAPI, UsernameOrEmailCheck, 6 | AvatarUploadAPI, UserProfileAPI, UserSettingAPI, SendEmailAuthAPI) 7 | 8 | from utils.captcha.views import CaptchaAPIView 9 | 10 | urlpatterns = [ 11 | path("login/", UserLoginAPI.as_view(), name="user_login_api"), 12 | path("logout/", UserLogoutAPI.as_view(), name="user_logout_api"), 13 | path("register/", UserRegisterAPI.as_view(), name="user_register_api"), 14 | path("delete_account/", DeleteAccountAPI.as_view(), name="delete_account_api"), 15 | path("email_auth/", EmailAuthAPI.as_view(), name="email_auth_api"), 16 | path("send_email_auth/", SendEmailAuthAPI.as_view(), name="send_email_auth_api"), 17 | path("change_password/", UserChangePasswordAPI.as_view(), name="user_change_password_api"), 18 | path("change_email/", UserChangeEmailAPI.as_view(), name="user_change_email_api"), 19 | path("apply_reset_password/", ApplyResetPasswordAPI.as_view(), name="apply_reset_password_api"), 20 | path("reset_password/", ResetPasswordAPI.as_view(), name="reset_password_api"), 21 | path("captcha/", CaptchaAPIView.as_view(), name="show_captcha"), 22 | path("check_username_or_email/", UsernameOrEmailCheck.as_view(), name="check_username_or_email"), 23 | path("profile/", UserProfileAPI.as_view(), name="user_profile_api"), 24 | path("user/", UserSettingAPI.as_view(), name="user_setting_api"), 25 | path("upload_avatar/", AvatarUploadAPI.as_view(), name="avatar_upload_api"), 26 | ] 27 | -------------------------------------------------------------------------------- /backend/account/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/account/views/__init__.py -------------------------------------------------------------------------------- /backend/announcement/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/announcement/__init__.py -------------------------------------------------------------------------------- /backend/announcement/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.12 on 2017-01-23 07:59 3 | from __future__ import unicode_literals 4 | 5 | import django.db.models.deletion 6 | from django.conf import settings 7 | from django.db import migrations, models 8 | 9 | import utils.models 10 | 11 | 12 | class Migration(migrations.Migration): 13 | 14 | initial = True 15 | 16 | dependencies = [ 17 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 18 | ] 19 | 20 | operations = [ 21 | migrations.CreateModel( 22 | name='Announcement', 23 | fields=[ 24 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 25 | ('title', models.CharField(max_length=50)), 26 | ('content', utils.models.RichTextField()), 27 | ('create_time', models.DateTimeField(auto_now_add=True)), 28 | ('last_update_time', models.DateTimeField(auto_now=True)), 29 | ('visible', models.BooleanField(default=True)), 30 | ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 31 | ], 32 | options={ 33 | 'db_table': 'announcement', 34 | }, 35 | ), 36 | ] 37 | -------------------------------------------------------------------------------- /backend/announcement/migrations/0002_auto_20171011_1214.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-10-11 12:14 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('announcement', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='announcement', 17 | name='title', 18 | field=models.CharField(max_length=64), 19 | ), 20 | migrations.AlterModelOptions( 21 | name='announcement', 22 | options={'ordering': ('-create_time',)}, 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /backend/announcement/migrations/0003_auto_20180501_0436.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.3 on 2018-05-01 04:36 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('announcement', '0002_auto_20171011_1214'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='announcement', 17 | name='title', 18 | field=models.TextField(), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /backend/announcement/migrations/0004_alter_announcement_id.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.5 on 2021-12-26 05:58 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('announcement', '0003_auto_20180501_0436'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='announcement', 15 | name='id', 16 | field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/announcement/migrations/0005_announcement_top_fixed.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.5 on 2021-12-31 08:40 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('announcement', '0004_alter_announcement_id'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='announcement', 15 | name='top_fixed', 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/announcement/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/announcement/migrations/__init__.py -------------------------------------------------------------------------------- /backend/announcement/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from account.models import User 4 | from utils.models import RichTextField 5 | 6 | 7 | class Announcement(models.Model): 8 | title = models.TextField() 9 | # HTML 10 | content = RichTextField() 11 | create_time = models.DateTimeField(auto_now_add=True) 12 | created_by = models.ForeignKey(User, on_delete=models.CASCADE) 13 | last_update_time = models.DateTimeField(auto_now=True) 14 | visible = models.BooleanField(default=True) 15 | top_fixed = models.BooleanField(default=False) 16 | 17 | class Meta: 18 | db_table = "announcement" 19 | ordering = ("-create_time",) 20 | -------------------------------------------------------------------------------- /backend/announcement/serializers.py: -------------------------------------------------------------------------------- 1 | from utils.api import serializers 2 | from utils.api._serializers import UsernameSerializer 3 | 4 | from .models import Announcement 5 | 6 | 7 | class CreateAnnouncementSerializer(serializers.Serializer): 8 | title = serializers.CharField(max_length=64) 9 | content = serializers.CharField(max_length=1024 * 1024 * 8) 10 | visible = serializers.BooleanField() 11 | top_fixed = serializers.BooleanField() 12 | 13 | 14 | class AnnouncementSerializer(serializers.ModelSerializer): 15 | created_by = UsernameSerializer() 16 | 17 | class Meta: 18 | model = Announcement 19 | fields = "__all__" 20 | 21 | 22 | class EditAnnouncementSerializer(serializers.Serializer): 23 | id = serializers.IntegerField() 24 | title = serializers.CharField(max_length=64) 25 | content = serializers.CharField(max_length=1024 * 1024 * 8) 26 | visible = serializers.BooleanField() 27 | top_fixed = serializers.BooleanField() 28 | -------------------------------------------------------------------------------- /backend/announcement/urls/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/announcement/urls/__init__.py -------------------------------------------------------------------------------- /backend/announcement/urls/admin.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from ..views.admin import AnnouncementAdminAPI 4 | 5 | urlpatterns = [ 6 | path("announcement/", AnnouncementAdminAPI.as_view(), name="announcement_admin_api"), 7 | ] 8 | -------------------------------------------------------------------------------- /backend/announcement/urls/oj.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from ..views.oj import AnnouncementAPI, AnnouncementDetailAPI 4 | 5 | urlpatterns = [ 6 | path("announcement/", AnnouncementAPI.as_view(), name="announcement_api"), 7 | path("announcement_detail/", AnnouncementDetailAPI.as_view(), name="announcement_detail_api"), 8 | ] 9 | -------------------------------------------------------------------------------- /backend/announcement/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/announcement/views/__init__.py -------------------------------------------------------------------------------- /backend/assignment/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/assignment/__init__.py -------------------------------------------------------------------------------- /backend/assignment/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.24 on 2021-07-30 13:44 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | import utils.models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | initial = True 12 | 13 | dependencies = [ 14 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 15 | ('course', '0001_initial'), 16 | ] 17 | 18 | operations = [ 19 | migrations.CreateModel( 20 | name='Assignment', 21 | fields=[ 22 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 23 | ('title', models.TextField()), 24 | ('content', utils.models.RichTextField()), 25 | ('start_time', models.DateTimeField()), 26 | ('end_time', models.DateTimeField()), 27 | ('create_time', models.DateTimeField(auto_now_add=True)), 28 | ('last_update_time', models.DateTimeField(auto_now=True)), 29 | ('visible', models.BooleanField(default=True)), 30 | ('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='course.Course')), 31 | ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 32 | ], 33 | options={ 34 | 'db_table': 'assignment', 35 | 'ordering': ('-start_time',), 36 | }, 37 | ), 38 | ] 39 | -------------------------------------------------------------------------------- /backend/assignment/migrations/0002_alter_assignment_id.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.5 on 2021-12-26 05:58 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('assignment', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='assignment', 15 | name='id', 16 | field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/assignment/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/assignment/migrations/__init__.py -------------------------------------------------------------------------------- /backend/assignment/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.utils.timezone import now 3 | 4 | from course.models import Course 5 | from account.models import User 6 | from utils.models import RichTextField 7 | from utils.constants import AssignmentStatus 8 | 9 | 10 | class Assignment(models.Model): 11 | created_by = models.ForeignKey(User, on_delete=models.CASCADE) 12 | course = models.ForeignKey(Course, on_delete=models.CASCADE) 13 | title = models.TextField() 14 | content = RichTextField() 15 | start_time = models.DateTimeField() 16 | end_time = models.DateTimeField() 17 | create_time = models.DateTimeField(auto_now_add=True) 18 | last_update_time = models.DateTimeField(auto_now=True) 19 | visible = models.BooleanField(default=True) 20 | 21 | @property 22 | def status(self): 23 | if self.start_time > now(): 24 | # NOT_START return 1 25 | return AssignmentStatus.ASSIGNMENT_NOT_START 26 | elif self.end_time < now(): 27 | # ENDED return -1 28 | return AssignmentStatus.ASSIGNMENT_ENDED 29 | else: 30 | # UNDERWAY return 0 31 | return AssignmentStatus.ASSIGNMENT_UNDERWAY 32 | 33 | def problem_details_permission(self, user): 34 | return user.is_authenticated and user.is_assignment_admin(self) 35 | 36 | class Meta: 37 | db_table = "assignment" 38 | ordering = ("-start_time",) 39 | -------------------------------------------------------------------------------- /backend/assignment/urls/professor.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from ..views.professor import AssignmentAPI, DownloadAssignmentSubmissions 4 | 5 | urlpatterns = [ 6 | path("course/assignment/", AssignmentAPI.as_view(), name="assignment_professor_api"), 7 | path("download_submissions/", DownloadAssignmentSubmissions.as_view(), name="download_assignment_submissions"), 8 | ] 9 | -------------------------------------------------------------------------------- /backend/assignment/urls/student.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from ..views.student import AssignmentAPI 4 | 5 | urlpatterns = [ 6 | path("course/assignment/", AssignmentAPI.as_view(), name="assignment_api"), 7 | ] 8 | -------------------------------------------------------------------------------- /backend/banner/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/banner/__init__.py -------------------------------------------------------------------------------- /backend/banner/fixtures/data.yaml: -------------------------------------------------------------------------------- 1 | - model: banner.banner 2 | pk: 1 3 | fields: 4 | title: banner1.png 5 | create_time: 2022-02-17 10:48:49.019174+00:00 6 | path: /public/upload/banner1.png 7 | visible: true 8 | 9 | - model: banner.banner 10 | pk: 2 11 | fields: 12 | title: banner2.png 13 | create_time: 2022-02-17 10:48:49.019174+00:00 14 | path: /public/upload/banner2.png 15 | visible: true 16 | 17 | - model: banner.banner 18 | pk: 3 19 | fields: 20 | title: banner3.png 21 | create_time: 2022-02-17 10:48:49.019174+00:00 22 | path: /public/upload/banner3.png 23 | visible: true 24 | 25 | - model: banner.banner 26 | pk: 4 27 | fields: 28 | title: banner4.png 29 | create_time: 2022-02-17 10:48:49.019174+00:00 30 | path: /public/upload/banner4.png 31 | visible: true 32 | -------------------------------------------------------------------------------- /backend/banner/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.9 on 2022-01-01 10:15 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Banner', 16 | fields=[ 17 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('title', models.TextField()), 19 | ('create_time', models.DateTimeField(auto_now_add=True)), 20 | ('path', models.TextField()), 21 | ('visible', models.BooleanField(default=False)), 22 | ], 23 | options={ 24 | 'db_table': 'banner', 25 | 'ordering': ('-create_time',), 26 | }, 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /backend/banner/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/banner/migrations/__init__.py -------------------------------------------------------------------------------- /backend/banner/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class Banner(models.Model): 5 | title = models.TextField() 6 | create_time = models.DateTimeField(auto_now_add=True) 7 | path = models.TextField() 8 | visible = models.BooleanField(default=False) 9 | 10 | class Meta: 11 | db_table = "banner" 12 | ordering = ("-create_time",) 13 | -------------------------------------------------------------------------------- /backend/banner/serializers.py: -------------------------------------------------------------------------------- 1 | from utils.api import serializers 2 | 3 | from .models import Banner 4 | 5 | 6 | class BannerSerializer(serializers.Serializer): 7 | path = serializers.CharField(max_length=1024) 8 | 9 | 10 | class BannerAdminSerializer(serializers.ModelSerializer): 11 | class Meta: 12 | model = Banner 13 | fields = "__all__" 14 | 15 | 16 | class CreateBannerSerializer(serializers.Serializer): 17 | title = serializers.CharField(max_length=64) 18 | path = serializers.CharField(max_length=1024) 19 | 20 | 21 | class EditBannerSerializer(serializers.Serializer): 22 | id = serializers.IntegerField() 23 | path = serializers.CharField() 24 | visible = serializers.BooleanField() 25 | -------------------------------------------------------------------------------- /backend/banner/tests.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/banner/tests.py -------------------------------------------------------------------------------- /backend/banner/urls/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/banner/urls/__init__.py -------------------------------------------------------------------------------- /backend/banner/urls/admin.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from ..views.admin import BannerAdminAPI 4 | 5 | urlpatterns = [ 6 | path("banner/", BannerAdminAPI.as_view(), name="banner_admin_api") 7 | ] 8 | -------------------------------------------------------------------------------- /backend/banner/urls/oj.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from ..views.oj import BannerAPI 4 | 5 | urlpatterns = [ 6 | path("banner/", BannerAPI.as_view(), name="banner_api") 7 | ] 8 | -------------------------------------------------------------------------------- /backend/banner/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/banner/views/__init__.py -------------------------------------------------------------------------------- /backend/banner/views/oj.py: -------------------------------------------------------------------------------- 1 | from drf_yasg.utils import swagger_auto_schema 2 | 3 | from banner.models import Banner 4 | from banner.serializers import BannerSerializer 5 | from utils.api import APIView 6 | 7 | 8 | class BannerAPI(APIView): 9 | @swagger_auto_schema( 10 | manual_parameters=[], 11 | operation_description="Get Banner Image List" 12 | ) 13 | def get(self, request): 14 | banners = Banner.objects.filter(visible=True) 15 | data = {} 16 | data["path"] = BannerSerializer(banners, many=True).data 17 | return self.success(data) 18 | -------------------------------------------------------------------------------- /backend/conf/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/conf/__init__.py -------------------------------------------------------------------------------- /backend/conf/migrations/0002_auto_20171011_1214.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-10-11 12:14 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('conf', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.DeleteModel( 16 | name='JudgeServerToken', 17 | ), 18 | migrations.DeleteModel( 19 | name='SMTPConfig', 20 | ), 21 | migrations.DeleteModel( 22 | name='WebsiteConfig', 23 | ), 24 | migrations.AlterField( 25 | model_name='judgeserver', 26 | name='hostname', 27 | field=models.CharField(max_length=128), 28 | ), 29 | migrations.AlterField( 30 | model_name='judgeserver', 31 | name='judger_version', 32 | field=models.CharField(max_length=32), 33 | ), 34 | migrations.AlterField( 35 | model_name='judgeserver', 36 | name='service_url', 37 | field=models.CharField(blank=True, max_length=256, null=True), 38 | ), 39 | ] 40 | -------------------------------------------------------------------------------- /backend/conf/migrations/0003_judgeserver_is_disabled.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-12-24 03:44 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('conf', '0002_auto_20171011_1214'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='judgeserver', 17 | name='is_disabled', 18 | field=models.BooleanField(default=False), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /backend/conf/migrations/0004_auto_20180501_0436.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.3 on 2018-05-01 04:36 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('conf', '0003_judgeserver_is_disabled'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='judgeserver', 17 | name='hostname', 18 | field=models.TextField(), 19 | ), 20 | migrations.AlterField( 21 | model_name='judgeserver', 22 | name='ip', 23 | field=models.TextField(null=True), 24 | ), 25 | migrations.AlterField( 26 | model_name='judgeserver', 27 | name='judger_version', 28 | field=models.TextField(), 29 | ), 30 | migrations.AlterField( 31 | model_name='judgeserver', 32 | name='service_url', 33 | field=models.TextField(null=True), 34 | ), 35 | ] 36 | -------------------------------------------------------------------------------- /backend/conf/migrations/0005_alter_judgeserver_id.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.5 on 2021-12-26 05:58 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('conf', '0004_auto_20180501_0436'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='judgeserver', 15 | name='id', 16 | field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/conf/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/conf/migrations/__init__.py -------------------------------------------------------------------------------- /backend/conf/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.utils import timezone 3 | from utils.shortcuts import get_env 4 | 5 | 6 | class JudgeServer(models.Model): 7 | hostname = models.TextField() 8 | ip = models.TextField(null=True) 9 | judger_version = models.TextField() 10 | cpu_core = models.IntegerField() 11 | memory_usage = models.FloatField() 12 | cpu_usage = models.FloatField() 13 | last_heartbeat = models.DateTimeField() 14 | create_time = models.DateTimeField(auto_now_add=True) 15 | task_number = models.IntegerField(default=0) 16 | service_url = models.TextField(null=True) 17 | is_disabled = models.BooleanField(default=False) 18 | 19 | @property 20 | def status(self): 21 | # In devcontainer, ignore heartbeat since server is not always running (PR#250) 22 | if get_env("DISABLE_HEARTBEAT"): 23 | return "normal" 24 | # Increase the one-second delay to improve adaptability to the network environment 25 | if (timezone.now() - self.last_heartbeat).total_seconds() > 6: 26 | return "abnormal" 27 | return "normal" 28 | 29 | class Meta: 30 | db_table = "judge_server" 31 | -------------------------------------------------------------------------------- /backend/conf/urls/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/conf/urls/__init__.py -------------------------------------------------------------------------------- /backend/conf/urls/admin.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from ..views import SMTPAPI, JudgeServerAPI, WebsiteConfigAPI, TestCasePruneAPI, SMTPTestAPI, IpAddressInfoAPI 4 | from ..views import ReleaseNotesAPI, DashboardInfoAPI 5 | 6 | urlpatterns = [ 7 | path("smtp/", SMTPAPI.as_view(), name="smtp_admin_api"), 8 | path("smtp_test/", SMTPTestAPI.as_view(), name="smtp_test_api"), 9 | path("website/", WebsiteConfigAPI.as_view(), name="website_config_api"), 10 | path("judge_server/", JudgeServerAPI.as_view(), name="judge_server_api"), 11 | path("prune_test_case/", TestCasePruneAPI.as_view(), name="prune_test_case_api"), 12 | path("versions/", ReleaseNotesAPI.as_view(), name="get_release_notes_api"), 13 | path("dashboard_info/", DashboardInfoAPI.as_view(), name="dashboard_info_api"), 14 | path("ip_info/", IpAddressInfoAPI.as_view(), name="ip_address_info_api"), 15 | ] 16 | -------------------------------------------------------------------------------- /backend/conf/urls/oj.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from ..views import JudgeServerHeartbeatAPI, LanguagesAPI, WebsiteConfigAPI 4 | 5 | urlpatterns = [ 6 | path("website/", WebsiteConfigAPI.as_view(), name="website_info_api"), 7 | path("judge_server_heartbeat/", JudgeServerHeartbeatAPI.as_view(), name="judge_server_heartbeat_api"), 8 | path("languages/", LanguagesAPI.as_view(), name="language_list_api") 9 | ] 10 | -------------------------------------------------------------------------------- /backend/conf/urls/professor.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from ..views import ProfessorDashboardInfoAPI 4 | 5 | urlpatterns = [ 6 | path("professor_dashboard_info/", ProfessorDashboardInfoAPI.as_view(), name="professor_dashboard_info_api"), 7 | ] 8 | -------------------------------------------------------------------------------- /backend/contest/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/contest/__init__.py -------------------------------------------------------------------------------- /backend/contest/migrations/0002_auto_20170209_0845.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.12 on 2017-02-09 08:45 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('contest', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='contestproblem', 17 | name='_id', 18 | field=models.CharField(db_index=True, default='1', max_length=24), 19 | preserve_default=False, 20 | ), 21 | migrations.RemoveField( 22 | model_name='contestproblem', 23 | name='sort_index', 24 | ), 25 | migrations.AlterUniqueTogether( 26 | name='contestproblem', 27 | unique_together=set([('_id', 'contest')]), 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /backend/contest/migrations/0003_auto_20170217_0820.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.12 on 2017-02-17 08:20 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('contest', '0002_auto_20170209_0845'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterUniqueTogether( 16 | name='contestproblem', 17 | unique_together=set([]), 18 | ), 19 | migrations.RemoveField( 20 | model_name='contestproblem', 21 | name='contest', 22 | ), 23 | migrations.RemoveField( 24 | model_name='contestproblem', 25 | name='created_by', 26 | ), 27 | migrations.RemoveField( 28 | model_name='contestproblem', 29 | name='tags', 30 | ), 31 | migrations.DeleteModel( 32 | name='ContestProblem', 33 | ), 34 | ] 35 | -------------------------------------------------------------------------------- /backend/contest/migrations/0004_auto_20170717_1324.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2017-07-17 13:24 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('contest', '0003_auto_20170217_0820'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterModelOptions( 16 | name='contest', 17 | options={'ordering': ('-create_time',)}, 18 | ), 19 | migrations.AlterModelOptions( 20 | name='contestannouncement', 21 | options={'ordering': ('-create_time',)}, 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /backend/contest/migrations/0005_auto_20170823_0918.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2017-08-23 09:18 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('contest', '0004_auto_20170717_1324'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RenameField( 16 | model_name='acmcontestrank', 17 | old_name='total_ac_number', 18 | new_name='accepted_number', 19 | ), 20 | migrations.RenameField( 21 | model_name='acmcontestrank', 22 | old_name='total_submission_number', 23 | new_name='submission_number', 24 | ), 25 | migrations.RenameField( 26 | model_name='oicontestrank', 27 | old_name='total_submission_number', 28 | new_name='submission_number', 29 | ), 30 | ] 31 | -------------------------------------------------------------------------------- /backend/contest/migrations/0006_auto_20171011_1214.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-10-11 12:14 3 | from __future__ import unicode_literals 4 | 5 | import django.contrib.postgres.fields.jsonb 6 | from django.db import migrations 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('contest', '0005_auto_20170823_0918'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='acmcontestrank', 18 | name='submission_info', 19 | field=django.contrib.postgres.fields.jsonb.JSONField(default=dict), 20 | ), 21 | migrations.AlterField( 22 | model_name='oicontestrank', 23 | name='submission_info', 24 | field=django.contrib.postgres.fields.jsonb.JSONField(default=dict), 25 | ), 26 | migrations.AlterModelOptions( 27 | name='contest', 28 | options={'ordering': ('-start_time',)}, 29 | ), 30 | ] 31 | -------------------------------------------------------------------------------- /backend/contest/migrations/0007_contestannouncement_visible.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-11-06 09:02 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('contest', '0006_auto_20171011_1214'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='contestannouncement', 17 | name='visible', 18 | field=models.BooleanField(default=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /backend/contest/migrations/0008_contest_allowed_ip_ranges.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-11-10 06:57 3 | from __future__ import unicode_literals 4 | 5 | import django.contrib.postgres.fields.jsonb 6 | from django.db import migrations 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('contest', '0007_contestannouncement_visible'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name='contest', 18 | name='allowed_ip_ranges', 19 | field=django.contrib.postgres.fields.jsonb.JSONField(default=list), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /backend/contest/migrations/0009_auto_20180501_0436.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.3 on 2018-05-01 04:36 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('contest', '0008_contest_allowed_ip_ranges'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='contest', 17 | name='password', 18 | field=models.TextField(null=True), 19 | ), 20 | migrations.AlterField( 21 | model_name='contest', 22 | name='rule_type', 23 | field=models.TextField(), 24 | ), 25 | migrations.AlterField( 26 | model_name='contest', 27 | name='title', 28 | field=models.TextField(), 29 | ), 30 | migrations.AlterField( 31 | model_name='contestannouncement', 32 | name='title', 33 | field=models.TextField(), 34 | ), 35 | ] 36 | -------------------------------------------------------------------------------- /backend/contest/migrations/0010_auto_20190326_0201.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.7 on 2019-03-26 02:01 2 | 3 | from django.conf import settings 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 11 | ('contest', '0009_auto_20180501_0436'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterUniqueTogether( 16 | name='acmcontestrank', 17 | unique_together={('user', 'contest')}, 18 | ), 19 | migrations.AlterUniqueTogether( 20 | name='oicontestrank', 21 | unique_together={('user', 'contest')}, 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /backend/contest/migrations/0012_rename_total_score_acmcontestrank_total_penalty.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.4 on 2021-11-04 12:42 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('contest', '0011_auto_20211030_1535'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RenameField( 14 | model_name='acmcontestrank', 15 | old_name='total_score', 16 | new_name='total_penalty', 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/contest/migrations/0013_contestannouncement_problem.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.10 on 2022-01-07 14:16 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('problem', '__first__'), 11 | ('contest', '0012_rename_total_score_acmcontestrank_total_penalty'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='contestannouncement', 17 | name='problem', 18 | field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, to='problem.problem'), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /backend/contest/migrations/0014_auto_20220211_1432.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.12 on 2022-02-11 05:32 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('group', '0004_rename_is_admin_groupmember_is_group_admin'), 10 | ('contest', '0013_contestannouncement_problem'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='contest', 16 | name='allowed_groups', 17 | field=models.ManyToManyField(to='group.Group'), 18 | ), 19 | migrations.AddField( 20 | model_name='contest', 21 | name='prize', 22 | field=models.JSONField(default=dict), 23 | ), 24 | migrations.AddField( 25 | model_name='contest', 26 | name='scoring', 27 | field=models.TextField(default='ACM-ICPC style'), 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /backend/contest/migrations/0015_auto_20220211_1433.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.12 on 2022-02-11 05:33 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('contest', '0014_auto_20220211_1432'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='contest', 15 | name='constraints', 16 | field=models.JSONField(default=list), 17 | ), 18 | migrations.AddField( 19 | model_name='contest', 20 | name='requirements', 21 | field=models.JSONField(default=list), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /backend/contest/migrations/0016_rename_prize_contest_prizes.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.12 on 2022-02-11 05:52 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('contest', '0015_auto_20220211_1433'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RenameField( 14 | model_name='contest', 15 | old_name='prize', 16 | new_name='prizes', 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/contest/migrations/0017_alter_contest_allowed_groups.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.12 on 2022-02-11 05:56 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('group', '0004_rename_is_admin_groupmember_is_group_admin'), 10 | ('contest', '0016_rename_prize_contest_prizes'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='contest', 16 | name='allowed_groups', 17 | field=models.ManyToManyField(blank=True, to='group.Group'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /backend/contest/migrations/0018_auto_20220225_1422.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.12 on 2022-02-25 05:22 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('contest', '0017_alter_contest_allowed_groups'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name='contest', 16 | name='prizes', 17 | ), 18 | migrations.CreateModel( 19 | name='ContestPrize', 20 | fields=[ 21 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 22 | ('color', models.TextField()), 23 | ('name', models.TextField()), 24 | ('reward', models.TextField()), 25 | ('contest', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contest.contest')), 26 | ], 27 | ), 28 | migrations.AddField( 29 | model_name='acmcontestrank', 30 | name='prize', 31 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='contest.contestprize'), 32 | ), 33 | migrations.AddField( 34 | model_name='oicontestrank', 35 | name='prize', 36 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='contest.contestprize'), 37 | ), 38 | ] 39 | -------------------------------------------------------------------------------- /backend/contest/migrations/0019_alter_contestprize_contest.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.12 on 2022-02-25 05:46 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('contest', '0018_auto_20220225_1422'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='contestprize', 16 | name='contest', 17 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='prizes', to='contest.contest'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /backend/contest/migrations/0020_auto_20220315_1654.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.12 on 2022-03-15 07:54 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ('contest', '0019_alter_contestprize_contest'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name='contest', 18 | name='bank_filter', 19 | field=models.JSONField(default=None, null=True), 20 | ), 21 | migrations.CreateModel( 22 | name='ProblemBank', 23 | fields=[ 24 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 25 | ('problem_list', models.TextField(null=True)), 26 | ('contest', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contest.contest')), 27 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 28 | ], 29 | options={ 30 | 'db_table': 'problem_bank', 31 | 'unique_together': {('user', 'contest')}, 32 | }, 33 | ), 34 | ] 35 | -------------------------------------------------------------------------------- /backend/contest/migrations/0021_contest_rank_penalty_visible.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.12 on 2022-04-10 08:15 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('contest', '0020_auto_20220315_1654'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='contest', 15 | name='rank_penalty_visible', 16 | field=models.BooleanField(default=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/contest/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/contest/migrations/__init__.py -------------------------------------------------------------------------------- /backend/contest/urls/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/contest/urls/__init__.py -------------------------------------------------------------------------------- /backend/contest/urls/admin.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from ..views.admin import ContestAnnouncementAPI, ContestAPI, ContestRankAPI, DownloadContestSubmissions 4 | 5 | urlpatterns = [ 6 | path("contest/", ContestAPI.as_view(), name="contest_admin_api"), 7 | path("contest/announcement/", ContestAnnouncementAPI.as_view(), name="contest_announcement_admin_api"), 8 | path("download_submissions/", DownloadContestSubmissions.as_view(), name="acm_contest_helper"), 9 | path("contest/rank/", ContestRankAPI.as_view(), name="contest_rank_api") 10 | ] 11 | -------------------------------------------------------------------------------- /backend/contest/urls/oj.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from ..views.oj import ContestAnnouncementListAPI 4 | from ..views.oj import ContestPasswordVerifyAPI, ContestAccessAPI 5 | from ..views.oj import ContestListAPI, ContestAPI, ContestRankAPI, ProblemBankAPI 6 | 7 | urlpatterns = [ 8 | path("contests/", ContestListAPI.as_view(), name="contest_list_api"), 9 | path("contest/", ContestAPI.as_view(), name="contest_api"), 10 | path("contest/password/", ContestPasswordVerifyAPI.as_view(), name="contest_password_api"), 11 | path("contest/announcement/", ContestAnnouncementListAPI.as_view(), name="contest_announcement_api"), 12 | path("contest/access/", ContestAccessAPI.as_view(), name="contest_access_api"), 13 | path("contest/rank/", ContestRankAPI.as_view(), name="contest_rank_api"), 14 | path("contest/bank/", ProblemBankAPI.as_view(), name="contest_bank_api"), 15 | ] 16 | -------------------------------------------------------------------------------- /backend/contest/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/contest/views/__init__.py -------------------------------------------------------------------------------- /backend/course/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/course/__init__.py -------------------------------------------------------------------------------- /backend/course/migrations/0002_auto_20210808_1709.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.24 on 2021-08-08 08:09 2 | 3 | from django.conf import settings 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 11 | ('course', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RenameModel( 16 | old_name='Takes', 17 | new_name='Registration', 18 | ), 19 | migrations.AlterModelTable( 20 | name='registration', 21 | table='registration', 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /backend/course/migrations/0003_auto_20211226_1458.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.5 on 2021-12-26 05:58 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('course', '0002_auto_20210808_1709'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='course', 15 | name='id', 16 | field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), 17 | ), 18 | migrations.AlterField( 19 | model_name='registration', 20 | name='id', 21 | field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /backend/course/migrations/0004_registration_bookmark.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.5 on 2021-12-26 07:28 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('course', '0003_auto_20211226_1458'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='registration', 15 | name='bookmark', 16 | field=models.BooleanField(default=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/course/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/course/migrations/__init__.py -------------------------------------------------------------------------------- /backend/course/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from account.models import User 4 | 5 | 6 | class Course(models.Model): 7 | created_by = models.ForeignKey(User, on_delete=models.CASCADE) 8 | title = models.TextField() 9 | course_code = models.TextField() 10 | class_number = models.IntegerField() 11 | registered_year = models.IntegerField() 12 | semester = models.IntegerField() 13 | 14 | class Meta: 15 | db_table = "course" 16 | ordering = ("-registered_year",) 17 | 18 | 19 | class Registration(models.Model): 20 | course = models.ForeignKey(Course, on_delete=models.CASCADE) 21 | user = models.ForeignKey(User, on_delete=models.CASCADE) 22 | bookmark = models.BooleanField(default=True) 23 | 24 | class Meta: 25 | db_table = "registration" 26 | -------------------------------------------------------------------------------- /backend/course/urls/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/course/urls/__init__.py -------------------------------------------------------------------------------- /backend/course/urls/professor.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from ..views.professor import BookmarkCourseAPI, CourseAPI, StudentManagementAPI 4 | 5 | urlpatterns = [ 6 | path("course/", CourseAPI.as_view(), name="course_professor_api"), 7 | path("course/students/", StudentManagementAPI.as_view(), name="student_management_api"), 8 | path("bookmark_course/", BookmarkCourseAPI.as_view(), name="bookmark_professor_api") 9 | ] 10 | -------------------------------------------------------------------------------- /backend/course/urls/student.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from ..views.student import BookmarkCourseAPI, CourseAPI 4 | 5 | urlpatterns = [ 6 | path("course/", CourseAPI.as_view(), name="course_api"), 7 | path("bookmark_course/", BookmarkCourseAPI.as_view(), name="bookmark_course_api"), 8 | ] 9 | -------------------------------------------------------------------------------- /backend/course/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/course/views/__init__.py -------------------------------------------------------------------------------- /backend/data/.gitignore: -------------------------------------------------------------------------------- 1 | **/* 2 | !.gitignore 3 | !config/ 4 | !config/.gitkeep 5 | !log/ 6 | !log/.gitkeep 7 | !public/ 8 | !public/avatar/ 9 | !public/avatar/default.png 10 | !public/upload/ 11 | !public/upload/banner[1234].png 12 | !ssl/ 13 | !ssl/.gitkeep 14 | !test_case/ 15 | !test_case/.gitkeep 16 | -------------------------------------------------------------------------------- /backend/data/config/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/data/config/.gitkeep -------------------------------------------------------------------------------- /backend/data/log/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/data/log/.gitkeep -------------------------------------------------------------------------------- /backend/data/public/avatar/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/data/public/avatar/default.png -------------------------------------------------------------------------------- /backend/data/public/upload/banner1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/data/public/upload/banner1.png -------------------------------------------------------------------------------- /backend/data/public/upload/banner2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/data/public/upload/banner2.png -------------------------------------------------------------------------------- /backend/data/public/upload/banner3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/data/public/upload/banner3.png -------------------------------------------------------------------------------- /backend/data/public/upload/banner4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/data/public/upload/banner4.png -------------------------------------------------------------------------------- /backend/data/public/website/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/data/public/website/favicon.ico -------------------------------------------------------------------------------- /backend/data/ssl/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/data/ssl/.gitkeep -------------------------------------------------------------------------------- /backend/data/test_case/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/data/test_case/.gitkeep -------------------------------------------------------------------------------- /backend/deploy/health_check.py: -------------------------------------------------------------------------------- 1 | from xmlrpc.client import Server 2 | 3 | if __name__ == "__main__": 4 | try: 5 | server = Server("http://localhost:9005/RPC2") 6 | info = server.supervisor.getAllProcessInfo() 7 | error_states = list(filter(lambda x: x["state"] != 20, info)) 8 | exit(len(error_states)) 9 | except Exception as e: 10 | print(e.with_traceback()) 11 | exit(1) 12 | -------------------------------------------------------------------------------- /backend/deploy/nginx/api_proxy.conf: -------------------------------------------------------------------------------- 1 | proxy_pass http://backend; 2 | proxy_set_header X-Real-IP __IP_HEADER__; 3 | proxy_set_header Host $http_host; 4 | client_max_body_size 200M; 5 | proxy_http_version 1.1; 6 | proxy_set_header Connection ''; -------------------------------------------------------------------------------- /backend/deploy/nginx/locations.conf: -------------------------------------------------------------------------------- 1 | location /public { 2 | root /data; 3 | } 4 | 5 | location /api { 6 | include api_proxy.conf; 7 | } 8 | 9 | location /admin { 10 | root /app/dist/admin; 11 | try_files $uri $uri/ /index.html =404; 12 | } 13 | 14 | location /professor { 15 | root /app/dist/professor; 16 | try_files $uri $uri/ /index.html =404; 17 | } 18 | 19 | location /.well-known { 20 | alias /data/ssl/.well-known; 21 | } 22 | 23 | location / { 24 | root /app/dist; 25 | try_files $uri $uri/ /index.html =404; 26 | } 27 | -------------------------------------------------------------------------------- /backend/deploy/requirements.txt: -------------------------------------------------------------------------------- 1 | asgiref==3.4.1 2 | attrs==20.3.0 3 | certifi==2020.12.5 4 | chardet==4.0.0 5 | click==8.0.1 6 | coreapi==2.3.3 7 | coreschema==0.0.4 8 | coverage==5.4 9 | Django==3.2.12 10 | django-dbconn-retry==0.1.5 11 | django-dramatiq==0.10.0 12 | django-redis==4.12.1 13 | djangorestframework==3.12.2 14 | dramatiq==1.10.0 15 | drf-yasg==1.20.0 16 | entrypoints==0.3 17 | Envelopes==0.4 18 | flake8==4.0.1 19 | flake8-coding==1.3.2 20 | flake8-quotes==3.2.0 21 | gunicorn==20.0.4 22 | h11==0.12.0 23 | idna==2.10 24 | inflection==0.5.1 25 | itypes==1.2.0 26 | Jinja2==2.11.3 27 | jsonfield==3.1.0 28 | jsonschema==3.2.0 29 | MarkupSafe==1.1.1 30 | mccabe==0.6.1 31 | packaging==20.9 32 | Pillow==9.0.1 33 | prometheus-client==0.9.0 34 | psycopg2-binary==2.8.6 35 | pycodestyle==2.8.0 36 | pyflakes==2.4.0 37 | pyparsing==2.4.7 38 | pyrsistent==0.17.3 39 | python-dateutil==2.8.1 40 | pytz==2020.5 41 | PyYAML==5.4.1 42 | redis==3.5.3 43 | requests==2.25.1 44 | ruamel.yaml==0.16.12 45 | ruamel.yaml.clib==0.2.2 46 | six==1.15.0 47 | sqlparse==0.4.2 48 | uritemplate==3.0.1 49 | urllib3==1.26.5 50 | uvicorn==0.15.0 51 | XlsxWriter==1.3.7 52 | -------------------------------------------------------------------------------- /backend/deploy/supervisord.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | logfile=/data/log/supervisord.log 3 | logfile_maxbytes=10MB 4 | logfile_backups=10 5 | loglevel=info 6 | pidfile=/tmp/supervisord.pid 7 | nodaemon=true 8 | childlogdir=/data/log/ 9 | 10 | [inet_http_server] 11 | port=127.0.0.1:9005 12 | 13 | [rpcinterface:supervisor] 14 | supervisor.rpcinterface_factory=supervisor.rpcinterface:make_main_rpcinterface 15 | 16 | [supervisorctl] 17 | serverurl=http://127.0.0.1:9005 18 | 19 | [program:nginx] 20 | command=nginx -c /app/deploy/nginx/nginx.conf 21 | directory=/app/ 22 | stdout_logfile=/data/log/nginx.log 23 | stderr_logfile=/data/log/nginx.log 24 | autostart=true 25 | autorestart=true 26 | startsecs=5 27 | stopwaitsecs=5 28 | killasgroup=true 29 | 30 | [program:gunicorn] 31 | command=gunicorn oj.asgi --user server --group spj --bind 127.0.0.1:8080 --workers %(ENV_MAX_WORKER_NUM)s --worker-class uvicorn.workers.UvicornWorker --threads 4 --max-requests-jitter 10000 --max-requests 1000000 --keep-alive 32 32 | directory=/app/ 33 | stdout_logfile=/data/log/gunicorn.log 34 | stderr_logfile=/data/log/gunicorn.log 35 | autostart=true 36 | autorestart=true 37 | startsecs=5 38 | stopwaitsecs=5 39 | killasgroup=true 40 | 41 | [program:dramatiq] 42 | command=python3 manage.py rundramatiq --processes %(ENV_MAX_WORKER_NUM)s --threads 4 43 | directory=/app/ 44 | user=nobody 45 | stdout_logfile=/data/log/dramatiq.log 46 | stderr_logfile=/data/log/dramatiq.log 47 | autostart=true 48 | autorestart=true 49 | startsecs=5 50 | stopwaitsecs=5 51 | killasgroup=true 52 | -------------------------------------------------------------------------------- /backend/deploy/test_case_rsync/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.6 2 | 3 | RUN apk add --update --no-cache rsync 4 | 5 | ADD ./run.sh /tmp/run.sh 6 | ADD ./rsyncd.conf /etc/rsyncd.conf 7 | 8 | CMD /bin/sh /tmp/run.sh -------------------------------------------------------------------------------- /backend/deploy/test_case_rsync/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | oj-rsync-master: 4 | image: oj_rsync 5 | container_name: oj-rsync 6 | volumes: 7 | - $PWD/data/backend/test_case:/test_case:ro 8 | - $PWD/data/rsync_master:/log 9 | environment: 10 | - RSYNC_MODE=master 11 | - RSYNC_USER=ojrsync 12 | - RSYNC_PASSWORD=CHANGE_THIS_PASSWORD 13 | ports: 14 | - "0.0.0.0:873:873" 15 | 16 | oj-rsync-slave: 17 | image: oj-rsync 18 | volumes: 19 | - $PWD/test_case:/test_case 20 | - $PWD/rsync_slave:/log 21 | environment: 22 | - RSYNC_MODE=slave 23 | - RSYNC_USER=ojrsync 24 | - RSYNC_PASSWORD=CHANGE_THIS_PASSWORD 25 | -------------------------------------------------------------------------------- /backend/deploy/test_case_rsync/rsyncd.conf: -------------------------------------------------------------------------------- 1 | port = 873 2 | uid = root 3 | gid = root 4 | use chroot = yes 5 | read only = yes 6 | log file = /log/rsyncd.log 7 | 8 | [testcase] 9 | path = /test_case/ 10 | list = yes 11 | auth users = ojrsync 12 | secrets file = /etc/rsyncd.passwd 13 | -------------------------------------------------------------------------------- /backend/deploy/test_case_rsync/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | slave_runner() 4 | { 5 | while true 6 | do 7 | rsync -avzP --delete --progress --password-file=/etc/rsync_slave.passwd $RSYNC_USER@$RSYNC_MASTER_ADDR::testcase /test_case >> /log/rsync_slave.log 8 | sleep 5 9 | done 10 | } 11 | 12 | master_runner() 13 | { 14 | rsync --daemon --config=/etc/rsyncd.conf 15 | while true 16 | do 17 | sleep 60 18 | done 19 | } 20 | 21 | if [ "$RSYNC_MODE" = "master" ]; then 22 | if [ ! -f "/etc/rsyncd.passwd" ]; then 23 | echo "$RSYNC_USER:$RSYNC_PASSWORD" > /etc/rsyncd.passwd 24 | fi 25 | chmod 600 /etc/rsyncd.passwd 26 | master_runner 27 | else 28 | if [ ! -f "/etc/rsync_slave.passwd" ]; then 29 | echo "$RSYNC_PASSWORD" > /etc/rsync_slave.passwd 30 | fi 31 | chmod 600 /etc/rsync_slave.passwd 32 | slave_runner 33 | fi 34 | -------------------------------------------------------------------------------- /backend/group/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/group/__init__.py -------------------------------------------------------------------------------- /backend/group/migrations/0002_alter_groupmember_is_admin.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.11 on 2022-02-02 17:18 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('group', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='groupmember', 15 | name='is_admin', 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/group/migrations/0003_auto_20220203_2234.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.11 on 2022-02-03 13:34 2 | 3 | from django.conf import settings 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 11 | ('group', '0002_alter_groupmember_is_admin'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RenameModel( 16 | old_name='GroupApplication', 17 | new_name='GroupMemberJoin', 18 | ), 19 | migrations.AlterModelTable( 20 | name='groupmemberjoin', 21 | table='group_member_join', 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /backend/group/migrations/0004_rename_is_admin_groupmember_is_group_admin.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.11 on 2022-02-05 01:56 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('group', '0003_auto_20220203_2234'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RenameField( 14 | model_name='groupmember', 15 | old_name='is_admin', 16 | new_name='is_group_admin', 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/group/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/group/migrations/__init__.py -------------------------------------------------------------------------------- /backend/group/models.py: -------------------------------------------------------------------------------- 1 | # from django.forms import ImageField 2 | from django.db import models 3 | 4 | from account.models import User 5 | from utils.models import RichTextField 6 | 7 | 8 | class GroupRegistrationRequest(models.Model): 9 | name = models.TextField() 10 | short_description = models.TextField() 11 | description = RichTextField() 12 | is_official = models.BooleanField() 13 | 14 | create_time = models.DateTimeField(auto_now_add=True) 15 | created_by = models.ForeignKey(User, on_delete=models.CASCADE) 16 | 17 | class Meta: 18 | db_table = "group_registration_request" 19 | 20 | 21 | class Group(models.Model): 22 | name = models.TextField() 23 | short_description = models.TextField() 24 | description = RichTextField() 25 | is_official = models.BooleanField() 26 | # logo = ImageField() 27 | 28 | created_by = models.ForeignKey(User, on_delete=models.PROTECT) 29 | 30 | create_time = models.DateTimeField(auto_now_add=True) 31 | last_update_time = models.DateTimeField(auto_now=True) 32 | 33 | members = models.ManyToManyField(User, related_name="groups", through="GroupMember", through_fields=("group", "user")) 34 | 35 | class Meta: 36 | db_table = "group" 37 | 38 | 39 | class GroupMember(models.Model): 40 | user = models.ForeignKey(User, on_delete=models.CASCADE) 41 | group = models.ForeignKey(Group, on_delete=models.CASCADE) 42 | 43 | is_group_admin = models.BooleanField(default=False) 44 | 45 | 46 | class GroupMemberJoin(models.Model): 47 | group = models.ForeignKey(Group, on_delete=models.CASCADE) 48 | created_by = models.ForeignKey(User, on_delete=models.CASCADE) 49 | description = RichTextField() 50 | 51 | class Meta: 52 | db_table = "group_member_join" 53 | -------------------------------------------------------------------------------- /backend/group/urls/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/group/urls/__init__.py -------------------------------------------------------------------------------- /backend/group/urls/admin.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from ..views.admin import AdminGroupRegistrationRequestAPI, GroupAdminAPI 3 | 4 | 5 | urlpatterns = [ 6 | path("group/registration_request/", AdminGroupRegistrationRequestAPI.as_view(), name="group_registration_request_admin_api"), 7 | path("group/", GroupAdminAPI.as_view(), name="group_admin_api") 8 | ] 9 | -------------------------------------------------------------------------------- /backend/group/urls/oj.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from ..views.oj import GroupMemberJoinAPI, GroupMemberAPI, GroupRegistrationRequestAPI, GroupAPI 3 | 4 | 5 | urlpatterns = [ 6 | path("group/registration_request/", GroupRegistrationRequestAPI.as_view(), name="group_registration_request_api"), 7 | path("group/", GroupAPI.as_view(), name="group_api"), 8 | path("group/member_join/", GroupMemberJoinAPI.as_view(), name="group_member_join_api"), 9 | path("group/member/", GroupMemberAPI.as_view(), name="group_member_api") 10 | ] 11 | -------------------------------------------------------------------------------- /backend/group/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/group/views/__init__.py -------------------------------------------------------------------------------- /backend/judge/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/judge/__init__.py -------------------------------------------------------------------------------- /backend/judge/tasks.py: -------------------------------------------------------------------------------- 1 | import dramatiq 2 | 3 | from account.models import User 4 | from submission.models import Submission 5 | from judge.dispatcher import JudgeDispatcher 6 | from utils.shortcuts import DRAMATIQ_WORKER_ARGS 7 | 8 | 9 | @dramatiq.actor(**DRAMATIQ_WORKER_ARGS()) 10 | def judge_task(submission_id, problem_id): 11 | uid = Submission.objects.get(id=submission_id).user_id 12 | if User.objects.get(id=uid).is_disabled: 13 | return 14 | JudgeDispatcher(submission_id, problem_id).judge() 15 | -------------------------------------------------------------------------------- /backend/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "oj.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | import django 10 | sys.stdout.write("Django VERSION " + str(django.VERSION) + "\n") 11 | 12 | execute_from_command_line(sys.argv) 13 | -------------------------------------------------------------------------------- /backend/oj/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/oj/__init__.py -------------------------------------------------------------------------------- /backend/oj/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for oj project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "oj.settings") 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /backend/oj/dev_settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | from utils.shortcuts import get_env 3 | 4 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 5 | 6 | DATABASES = { 7 | 'default': { 8 | 'ENGINE': 'django.db.backends.postgresql', 9 | 'HOST': get_env('POSTGRES_HOST', '127.0.0.1'), 10 | 'PORT': get_env('POSTGRES_PORT', '5432'), 11 | 'NAME': "codingplatform", 12 | 'USER': "codingplatform", 13 | 'PASSWORD': 'codingplatform' 14 | } 15 | } 16 | 17 | REDIS_CONF = { 18 | "host": get_env('REDIS_HOST', "127.0.0.1"), 19 | "port": get_env('REDIS_PORT', "6379") 20 | } 21 | 22 | 23 | DEBUG = True 24 | 25 | ALLOWED_HOSTS = ["*"] 26 | 27 | DATA_DIR = f"{BASE_DIR}/data" 28 | -------------------------------------------------------------------------------- /backend/oj/production_settings.py: -------------------------------------------------------------------------------- 1 | from utils.shortcuts import get_env 2 | 3 | DATABASES = { 4 | 'default': { 5 | 'ENGINE': 'django.db.backends.postgresql', 6 | 'HOST': get_env("POSTGRES_HOST", "oj-postgres"), 7 | 'PORT': get_env("POSTGRES_PORT", "5432"), 8 | 'NAME': get_env("POSTGRES_DB"), 9 | 'USER': get_env("POSTGRES_USER"), 10 | 'PASSWORD': get_env("POSTGRES_PASSWORD") 11 | } 12 | } 13 | 14 | REDIS_CONF = { 15 | "host": get_env("REDIS_HOST", "oj-redis"), 16 | "port": get_env("REDIS_PORT", "6379") 17 | } 18 | 19 | DEBUG = False 20 | 21 | ALLOWED_HOSTS = ['*'] 22 | 23 | DATA_DIR = "/data" 24 | -------------------------------------------------------------------------------- /backend/oj/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import include, path 2 | 3 | 4 | urlpatterns = [ 5 | path("api/", include("account.urls.oj")), 6 | path("api/admin/", include("account.urls.admin")), 7 | path("api/", include("announcement.urls.oj")), 8 | path("api/admin/", include("announcement.urls.admin")), 9 | path("api/", include("conf.urls.oj")), 10 | path("api/admin/", include("conf.urls.admin")), 11 | path("api/professor/", include("conf.urls.professor")), 12 | path("api/", include("problem.urls.oj")), 13 | path("api/admin/", include("problem.urls.admin")), 14 | path("api/lecture/", include("problem.urls.student")), 15 | path("api/lecture/professor/", include("problem.urls.professor")), 16 | path("api/", include("contest.urls.oj")), 17 | path("api/admin/", include("contest.urls.admin")), 18 | path("api/", include("submission.urls")), 19 | path("api/admin/", include("utils.urls")), 20 | path("api/", include("banner.urls.oj")), 21 | path("api/admin/", include("banner.urls.admin")), 22 | path("api/", include("group.urls.oj")), 23 | path("api/admin/", include("group.urls.admin")), 24 | path("api/lecture/", include("course.urls.student")), 25 | path("api/lecture/professor/", include("course.urls.professor")), 26 | path("api/lecture/", include("assignment.urls.student")), 27 | path("api/lecture/professor/", include("assignment.urls.professor")), 28 | path("api/lecture/", include("qna.urls.student")), 29 | path("api/lecture/professor/", include("qna.urls.professor")), 30 | ] 31 | -------------------------------------------------------------------------------- /backend/oj/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for qduoj project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "oj.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /backend/options/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/options/__init__.py -------------------------------------------------------------------------------- /backend/options/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-10-23 08:11 3 | from __future__ import unicode_literals 4 | 5 | import django.contrib.postgres.fields.jsonb 6 | from django.db import migrations, models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | initial = True 12 | 13 | dependencies = [ 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='SysOptions', 19 | fields=[ 20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('key', models.CharField(db_index=True, max_length=128, unique=True)), 22 | ('value', django.contrib.postgres.fields.jsonb.JSONField()), 23 | ], 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /backend/options/migrations/0002_auto_20180501_0436.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.3 on 2018-05-01 04:36 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('options', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='sysoptions', 17 | name='key', 18 | field=models.TextField(db_index=True, unique=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /backend/options/migrations/0003_migrate_languages_options.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.3 on 2018-05-01 04:36 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('options', '0002_auto_20180501_0436'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RunSQL(""" 16 | DELETE FROM options_sysoptions WHERE key = 'languages'; 17 | """) 18 | ] 19 | -------------------------------------------------------------------------------- /backend/options/migrations/0004_auto_20211030_1535.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.4 on 2021-10-30 06:35 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('options', '0003_migrate_languages_options'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='sysoptions', 15 | name='id', 16 | field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), 17 | ), 18 | migrations.AlterField( 19 | model_name='sysoptions', 20 | name='value', 21 | field=models.JSONField(), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /backend/options/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/options/migrations/__init__.py -------------------------------------------------------------------------------- /backend/options/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.db.models import JSONField 3 | 4 | 5 | class SysOptions(models.Model): 6 | key = models.TextField(unique=True, db_index=True) 7 | value = JSONField() 8 | -------------------------------------------------------------------------------- /backend/options/tests.py: -------------------------------------------------------------------------------- 1 | # Create your tests here. 2 | -------------------------------------------------------------------------------- /backend/options/views.py: -------------------------------------------------------------------------------- 1 | # Create your views here. 2 | -------------------------------------------------------------------------------- /backend/problem/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/problem/__init__.py -------------------------------------------------------------------------------- /backend/problem/migrations/0002_problem__id.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.12 on 2017-02-09 08:45 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('problem', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='problem', 17 | name='_id', 18 | field=models.CharField(db_index=True, default='1', max_length=24, unique=True), 19 | preserve_default=False, 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /backend/problem/migrations/0004_auto_20170501_0637.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2017-05-01 06:37 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('problem', '0003_auto_20170217_0820'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='contestproblem', 17 | name='total_accepted_number', 18 | field=models.BigIntegerField(default=0), 19 | ), 20 | migrations.AlterField( 21 | model_name='contestproblem', 22 | name='total_submit_number', 23 | field=models.BigIntegerField(default=0), 24 | ), 25 | migrations.AlterField( 26 | model_name='problem', 27 | name='total_accepted_number', 28 | field=models.BigIntegerField(default=0), 29 | ), 30 | migrations.AlterField( 31 | model_name='problem', 32 | name='total_submit_number', 33 | field=models.BigIntegerField(default=0), 34 | ), 35 | ] 36 | -------------------------------------------------------------------------------- /backend/problem/migrations/0005_auto_20170815_1258.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2017-08-15 12:58 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | import jsonfield.fields 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('problem', '0004_auto_20170501_0637'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name='contestproblem', 18 | name='statistic_info', 19 | field=jsonfield.fields.JSONField(default={}), 20 | ), 21 | migrations.AddField( 22 | model_name='problem', 23 | name='statistic_info', 24 | field=jsonfield.fields.JSONField(default={}), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /backend/problem/migrations/0006_auto_20170823_0918.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2017-08-23 09:18 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('problem', '0005_auto_20170815_1258'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RenameField( 16 | model_name='contestproblem', 17 | old_name='total_accepted_number', 18 | new_name='accepted_number', 19 | ), 20 | migrations.RenameField( 21 | model_name='contestproblem', 22 | old_name='total_submit_number', 23 | new_name='submission_number', 24 | ), 25 | migrations.RenameField( 26 | model_name='problem', 27 | old_name='total_accepted_number', 28 | new_name='accepted_number', 29 | ), 30 | migrations.RenameField( 31 | model_name='problem', 32 | old_name='total_submit_number', 33 | new_name='submission_number', 34 | ), 35 | ] 36 | -------------------------------------------------------------------------------- /backend/problem/migrations/0009_auto_20171011_1214.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-10-11 12:14 3 | from __future__ import unicode_literals 4 | 5 | import django.contrib.postgres.fields.jsonb 6 | from django.db import migrations 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('problem', '0008_auto_20170923_1318'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='problem', 18 | name='languages', 19 | field=django.contrib.postgres.fields.jsonb.JSONField(), 20 | ), 21 | migrations.AlterField( 22 | model_name='problem', 23 | name='samples', 24 | field=django.contrib.postgres.fields.jsonb.JSONField(), 25 | ), 26 | migrations.AlterField( 27 | model_name='problem', 28 | name='statistic_info', 29 | field=django.contrib.postgres.fields.jsonb.JSONField(default=dict), 30 | ), 31 | migrations.AlterField( 32 | model_name='problem', 33 | name='template', 34 | field=django.contrib.postgres.fields.jsonb.JSONField(), 35 | ), 36 | migrations.AlterField( 37 | model_name='problem', 38 | name='test_case_score', 39 | field=django.contrib.postgres.fields.jsonb.JSONField(), 40 | ), 41 | migrations.AlterModelOptions( 42 | name='problem', 43 | options={'ordering': ('create_time',)}, 44 | ), 45 | ] 46 | -------------------------------------------------------------------------------- /backend/problem/migrations/0010_problem_spj_compile_ok.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-11-16 12:42 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('problem', '0009_auto_20171011_1214'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='problem', 17 | name='spj_compile_ok', 18 | field=models.BooleanField(default=False), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /backend/problem/migrations/0011_fix_problem_ac_count.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations 5 | from django.db.models import Count 6 | 7 | 8 | def fix_problem_count_bugs(apps, schema_editor): 9 | Submission = apps.get_model("submission", "Submission") 10 | Problem = apps.get_model("problem", "Problem") 11 | 12 | for item in Problem.objects.filter(contest__isnull=True): 13 | submissions = Submission.objects.filter(problem=item) 14 | item.submission_number = submissions.count() 15 | results_count = submissions.values('result').annotate(count=Count('result')).order_by('result') 16 | info = dict() 17 | item.accepted_number = 0 18 | for stat in results_count: 19 | result = stat["result"] 20 | if result == 0: 21 | item.accepted_number = stat["count"] 22 | info[str(result)] = stat["count"] 23 | item.statistic_info = info 24 | item.save(update_fields=["submission_number", "accepted_number", "statistic_info"]) 25 | 26 | 27 | class Migration(migrations.Migration): 28 | dependencies = [ 29 | ('problem', '0010_problem_spj_compile_ok'), 30 | ('submission', '0009_delete_user_output'), 31 | ] 32 | 33 | operations = [ 34 | migrations.RunPython(fix_problem_count_bugs, reverse_code=migrations.RunPython.noop) 35 | ] 36 | -------------------------------------------------------------------------------- /backend/problem/migrations/0013_problem_io_mode.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.7 on 2019-03-12 07:13 2 | 3 | import django.contrib.postgres.fields.jsonb 4 | from django.db import migrations 5 | import problem.models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('problem', '0012_auto_20180501_0436'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='problem', 17 | name='io_mode', 18 | field=django.contrib.postgres.fields.jsonb.JSONField(default=problem.models._default_io_mode), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /backend/problem/migrations/0014_problem_share_submission.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.7 on 2019-03-13 09:38 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('problem', '0013_problem_io_mode'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='problem', 15 | name='share_submission', 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/problem/migrations/0015_auto_20210730_2244.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.24 on 2021-07-30 13:44 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('assignment', '0001_initial'), 11 | ('problem', '0014_problem_share_submission'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='problem', 17 | name='assignment', 18 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='assignment.Assignment'), 19 | ), 20 | migrations.AddField( 21 | model_name='problem', 22 | name='type', 23 | field=models.TextField(default='Problem'), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /backend/problem/migrations/0016_auto_20210801_2045.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.24 on 2021-08-01 11:45 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('problem', '0015_auto_20210730_2244'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='problem', 16 | name='assignment', 17 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='problems', to='assignment.Assignment'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /backend/problem/migrations/0017_auto_20210814_1415.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.24 on 2021-08-14 05:15 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('problem', '0016_auto_20210801_2045'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='problem', 15 | name='type', 16 | field=models.TextField(default='Problem', null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/problem/migrations/0019_problem_bank.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.12 on 2022-03-10 08:20 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('problem', '0018_auto_20211226_1458'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='problem', 15 | name='bank', 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/problem/migrations/0020_auto_20220327_2013.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.12 on 2022-03-27 11:13 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('problem', '0019_problem_bank'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='ProblemSetGroup', 16 | fields=[ 17 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('title', models.TextField()), 19 | ('button_type', models.TextField()), 20 | ('is_disabled', models.BooleanField()), 21 | ], 22 | ), 23 | migrations.CreateModel( 24 | name='ProblemSet', 25 | fields=[ 26 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 27 | ('title', models.TextField()), 28 | ('color', models.TextField()), 29 | ('is_disabled', models.BooleanField()), 30 | ('is_public', models.BooleanField()), 31 | ('problem_set_group', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='problem_set', to='problem.problemsetgroup')), 32 | ], 33 | ), 34 | migrations.AddField( 35 | model_name='problem', 36 | name='problem_set', 37 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='problem.problemset'), 38 | ), 39 | ] 40 | -------------------------------------------------------------------------------- /backend/problem/migrations/0021_auto_20220329_1451.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.12 on 2022-03-29 05:51 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('problem', '0020_auto_20220327_2013'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='problem', 15 | name='problem_set', 16 | ), 17 | migrations.AddField( 18 | model_name='problem', 19 | name='problem_set', 20 | field=models.ManyToManyField(null=True, to='problem.ProblemSet'), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /backend/problem/migrations/0022_alter_problem_problem_set.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.12 on 2022-03-29 05:52 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('problem', '0021_auto_20220329_1451'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='problem', 15 | name='problem_set', 16 | field=models.ManyToManyField(blank=True, to='problem.ProblemSet'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/problem/migrations/0023_auto_20220329_1517.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.12 on 2022-03-29 06:17 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('problem', '0022_alter_problem_problem_set'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='problem', 15 | name='problem_set', 16 | ), 17 | migrations.AddField( 18 | model_name='problemset', 19 | name='problems', 20 | field=models.ManyToManyField(blank=True, to='problem.Problem'), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /backend/problem/migrations/0024_alter_problemset_problems.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.12 on 2022-03-29 16:43 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('problem', '0023_auto_20220329_1517'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='problemset', 15 | name='problems', 16 | field=models.ManyToManyField(blank=True, related_name='problem_set', to='problem.Problem'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/problem/migrations/0025_alter_problemset_problem_set_group.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.12 on 2022-03-30 06:33 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('problem', '0024_alter_problemset_problems'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='problemset', 16 | name='problem_set_group', 17 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='problem_set', to='problem.problemsetgroup'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /backend/problem/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/problem/migrations/__init__.py -------------------------------------------------------------------------------- /backend/problem/urls/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/problem/urls/__init__.py -------------------------------------------------------------------------------- /backend/problem/urls/admin.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from ..views.admin import (ContestProblemAPI, ProblemAPI, ProblemLevelAPIView, TestCaseAPI, MakeContestProblemPublicAPIView, 4 | CompileSPJAPI, AddContestProblemAPI, TestCaseTextAPI, ProblemSetGroupAPI, ProblemSetAPI) 5 | 6 | urlpatterns = [ 7 | path("test_case/", TestCaseAPI.as_view(), name="test_case_api"), 8 | path("compile_spj/", CompileSPJAPI.as_view(), name="compile_spj"), 9 | path("problem/", ProblemAPI.as_view(), name="problem_admin_api"), 10 | path("contest/problem/", ContestProblemAPI.as_view(), name="contest_problem_admin_api"), 11 | path("contest_problem/make_public/", MakeContestProblemPublicAPIView.as_view(), name="make_public_api"), 12 | path("contest/add_problem_from_public/", AddContestProblemAPI.as_view(), name="add_contest_problem_from_public_api"), 13 | path("testcase_text/", TestCaseTextAPI.as_view(), name="testcase_text_api"), 14 | path("problem_level_count/", ProblemLevelAPIView.as_view(), name="problem_level_count"), 15 | path("problemset/group/", ProblemSetGroupAPI.as_view(), name="problem_set_group_admin_api"), 16 | path("problemset/", ProblemSetAPI.as_view(), name="problem_set_admin_api"), 17 | ] 18 | -------------------------------------------------------------------------------- /backend/problem/urls/oj.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from ..views.oj import ProblemTagAPI, ProblemAPI, ContestProblemAPI, BankProblemAPI, ProblemSetGroupAPI, ProblemSetAPI 4 | 5 | urlpatterns = [ 6 | path("problem/tags/", ProblemTagAPI.as_view(), name="problem_tag_list_api"), 7 | path("problem/", ProblemAPI.as_view(), name="problem_api"), 8 | path("contest/problem/", ContestProblemAPI.as_view(), name="contest_problem_api"), 9 | path("bank/problem/", BankProblemAPI.as_view(), name="bank_problem_api"), 10 | path("problemset/group/", ProblemSetGroupAPI.as_view(), name="problem_set_group_api"), 11 | path("problemset/", ProblemSetAPI.as_view(), name="problem_set_api"), 12 | ] 13 | -------------------------------------------------------------------------------- /backend/problem/urls/professor.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from ..views.professor import AssignmentProblemAPI, AddAssignmentProblemAPI 4 | 5 | urlpatterns = [ 6 | path("course/assignment/problem/", AssignmentProblemAPI.as_view(), name="assignment_problem_professor_api"), 7 | path("course/assignment/add_problem_from_public/", AddAssignmentProblemAPI.as_view(), name="add_assignment_problem_from_public_api"), 8 | ] 9 | -------------------------------------------------------------------------------- /backend/problem/urls/student.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from ..views.student import AssignmentProblemAPI 4 | 5 | urlpatterns = [ 6 | path("course/assignment/problem/", AssignmentProblemAPI.as_view(), name="assignment_problem_student_api"), 7 | ] 8 | -------------------------------------------------------------------------------- /backend/problem/utils.py: -------------------------------------------------------------------------------- 1 | import re 2 | from functools import lru_cache 3 | 4 | 5 | TEMPLATE_BASE = """//PREPEND BEGIN 6 | {} 7 | //PREPEND END 8 | 9 | //TEMPLATE BEGIN 10 | {} 11 | //TEMPLATE END 12 | 13 | //APPEND BEGIN 14 | {} 15 | //APPEND END""" 16 | 17 | 18 | @lru_cache(maxsize=100) 19 | def parse_problem_template(template_str): 20 | prepend = re.findall(r"//PREPEND BEGIN\n([\s\S]+?)//PREPEND END", template_str) 21 | template = re.findall(r"//TEMPLATE BEGIN\n([\s\S]+?)//TEMPLATE END", template_str) 22 | append = re.findall(r"//APPEND BEGIN\n([\s\S]+?)//APPEND END", template_str) 23 | return {"prepend": prepend[0] if prepend else "", 24 | "template": template[0] if template else "", 25 | "append": append[0] if append else ""} 26 | 27 | 28 | @lru_cache(maxsize=100) 29 | def build_problem_template(prepend, template, append): 30 | return TEMPLATE_BASE.format(prepend, template, append) 31 | -------------------------------------------------------------------------------- /backend/problem/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/problem/views/__init__.py -------------------------------------------------------------------------------- /backend/qna/migrations/0002_question_content_question_course_id_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.1 on 2022-01-09 08:54 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('course', '0004_registration_bookmark'), 11 | ('qna', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='question', 17 | name='content', 18 | field=models.TextField(null=True), 19 | ), 20 | migrations.AddField( 21 | model_name='question', 22 | name='course_id', 23 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='course.course'), 24 | ), 25 | migrations.AddField( 26 | model_name='question', 27 | name='last_update_time', 28 | field=models.DateTimeField(null=True), 29 | ), 30 | migrations.AddField( 31 | model_name='question', 32 | name='status', 33 | field=models.BooleanField(null=True), 34 | ), 35 | migrations.AddField( 36 | model_name='question', 37 | name='title', 38 | field=models.TextField(null=True), 39 | ), 40 | ] 41 | -------------------------------------------------------------------------------- /backend/qna/migrations/0003_alter_question_content_alter_question_course_id_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.1 on 2022-01-09 08:56 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('course', '0004_registration_bookmark'), 11 | ('qna', '0002_question_content_question_course_id_and_more'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='question', 17 | name='content', 18 | field=models.TextField(), 19 | ), 20 | migrations.AlterField( 21 | model_name='question', 22 | name='course_id', 23 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='course.course'), 24 | ), 25 | migrations.AlterField( 26 | model_name='question', 27 | name='last_update_time', 28 | field=models.DateTimeField(), 29 | ), 30 | migrations.AlterField( 31 | model_name='question', 32 | name='status', 33 | field=models.BooleanField(), 34 | ), 35 | migrations.AlterField( 36 | model_name='question', 37 | name='title', 38 | field=models.TextField(), 39 | ), 40 | ] 41 | -------------------------------------------------------------------------------- /backend/qna/migrations/0004_rename_course_id_question_course_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.1 on 2022-01-10 11:55 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('qna', '0003_alter_question_content_alter_question_course_id_and_more'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RenameField( 14 | model_name='question', 15 | old_name='course_id', 16 | new_name='course', 17 | ), 18 | migrations.AlterField( 19 | model_name='question', 20 | name='last_update_time', 21 | field=models.DateTimeField(auto_now=True), 22 | ), 23 | migrations.AlterField( 24 | model_name='question', 25 | name='status', 26 | field=models.BooleanField(default=True), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /backend/qna/migrations/0005_rename_status_question_is_open.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.4 on 2022-02-20 23:43 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('qna', '0004_rename_course_id_question_course_and_more'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RenameField( 14 | model_name='question', 15 | old_name='status', 16 | new_name='is_open', 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/qna/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/qna/migrations/__init__.py -------------------------------------------------------------------------------- /backend/qna/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from account.models import User 4 | from course.models import Course 5 | 6 | 7 | class AdminType(object): 8 | PROFESSOR = "Professor" 9 | CREATOR = "Creator" 10 | NONE = "None" 11 | 12 | 13 | class Question(models.Model): 14 | created_by = models.ForeignKey(User, on_delete=models.CASCADE) 15 | course = models.ForeignKey(Course, on_delete=models.CASCADE) 16 | title = models.TextField() 17 | content = models.TextField() 18 | create_time = models.DateTimeField(auto_now_add=True) 19 | last_update_time = models.DateTimeField(auto_now=True) 20 | is_open = models.BooleanField(default=True) 21 | 22 | class Meta: 23 | db_table = "question" 24 | ordering = ("-create_time",) 25 | 26 | 27 | class Answer(models.Model): 28 | question = models.ForeignKey(Question, on_delete=models.CASCADE) 29 | created_by = models.ForeignKey(User, on_delete=models.CASCADE) 30 | content = models.TextField() 31 | create_time = models.DateTimeField(auto_now_add=True) 32 | last_update_time = models.DateTimeField(auto_now=True) 33 | admin_type = models.TextField(default=AdminType.NONE) 34 | 35 | class Meta: 36 | db_table = "answer" 37 | ordering = ("-create_time",) 38 | 39 | def update_admin_type(self, user): 40 | if user.is_authenticated and user.is_admin_role(): 41 | self.admin_type = AdminType.PROFESSOR 42 | elif user.is_question_admin(self.question): 43 | self.admin_type = AdminType.CREATOR 44 | self.save(update_fields=["admin_type"]) 45 | 46 | def name(self): 47 | if self.admin_type == AdminType.PROFESSOR: 48 | return self.created_by.userprofile.real_name 49 | return self.created_by.username 50 | -------------------------------------------------------------------------------- /backend/qna/serializers.py: -------------------------------------------------------------------------------- 1 | from utils.api import UsernameSerializer, serializers 2 | from .models import Question 3 | from course.serializers import CourseSerializer 4 | 5 | 6 | class QuestionSerializer(serializers.ModelSerializer): 7 | created_by = UsernameSerializer() 8 | course = CourseSerializer() 9 | 10 | class Meta: 11 | model = Question 12 | fields = "__all__" 13 | 14 | 15 | class CreateQuestionSerializer(serializers.Serializer): 16 | course_id = serializers.IntegerField() 17 | title = serializers.CharField(max_length=128) 18 | content = serializers.CharField(max_length=1024 * 1024 * 8) 19 | 20 | 21 | class EditQuestionSerializer(serializers.Serializer): 22 | id = serializers.IntegerField() 23 | title = serializers.CharField(max_length=128) 24 | content = serializers.CharField(max_length=1024 * 1024 * 8) 25 | is_open = serializers.BooleanField() 26 | 27 | 28 | class AnswerSerializer(serializers.Serializer): 29 | id = serializers.IntegerField() 30 | name = serializers.CharField() 31 | admin_type = serializers.CharField() 32 | content = serializers.CharField() 33 | last_update_time = serializers.DateTimeField() 34 | 35 | 36 | class CreateAnswerSerializer(serializers.Serializer): 37 | question_id = serializers.IntegerField() 38 | content = serializers.CharField() 39 | closure = serializers.BooleanField() 40 | 41 | 42 | class EditAnswerSerializer(serializers.Serializer): 43 | id = serializers.IntegerField() 44 | content = serializers.CharField() 45 | -------------------------------------------------------------------------------- /backend/qna/urls/professor.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from ..views.professor import QuestionAPI 4 | 5 | urlpatterns = [ 6 | path("course/question/", QuestionAPI.as_view(), name="question_professor_api"), 7 | ] 8 | -------------------------------------------------------------------------------- /backend/qna/urls/student.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from ..views.student import QuestionAPI, AnswerAPI 4 | 5 | urlpatterns = [ 6 | path("course/question/", QuestionAPI.as_view(), name="question_api"), 7 | path("course/question/answer/", AnswerAPI.as_view(), name="answer_api") 8 | ] 9 | -------------------------------------------------------------------------------- /backend/qna/views/professor.py: -------------------------------------------------------------------------------- 1 | from utils.decorators import admin_role_required 2 | from drf_yasg.utils import swagger_auto_schema 3 | from drf_yasg import openapi 4 | 5 | from utils.api import APIView 6 | from ..serializers import QuestionSerializer 7 | from ..models import Question 8 | 9 | 10 | class QuestionAPI(APIView): 11 | @swagger_auto_schema( 12 | manual_parameters=[ 13 | openapi.Parameter( 14 | name="course_id", 15 | in_=openapi.IN_QUERY, 16 | description="Id of course", 17 | required=True, 18 | type=openapi.TYPE_INTEGER, 19 | ), 20 | openapi.Parameter( 21 | name="question_id", in_=openapi.IN_QUERY, 22 | type=openapi.TYPE_INTEGER, 23 | required=False 24 | ), 25 | ], 26 | operation_description="Get Question" 27 | ) 28 | @admin_role_required 29 | def get(self, request): 30 | course_id = request.GET.get("course_id") 31 | question_id = request.GET.get("question_id") 32 | if question_id: 33 | try: 34 | question = Question.objects.get(id=question_id, course_id=course_id) 35 | return self.success(QuestionSerializer(question).data) 36 | except Question.DoesNotExist: 37 | return self.error("Question does not exist") 38 | question = Question.objects.all().order_by("-create_time") 39 | 40 | return self.success(self.paginate_data(request, question, QuestionSerializer)) 41 | -------------------------------------------------------------------------------- /backend/run_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import getopt 3 | import os 4 | import sys 5 | 6 | opts, args = getopt.getopt(sys.argv[1:], "cm:", ["coverage=", "module="]) 7 | 8 | is_coverage = False 9 | test_module = "" 10 | setting = "oj.settings" 11 | 12 | for opt, arg in opts: 13 | if opt in ["-c", "--coverage"]: 14 | is_coverage = True 15 | if opt in ["-m", "--module"]: 16 | test_module = arg 17 | 18 | print(f"Coverage: {is_coverage}") 19 | print(f"Module: {(test_module if test_module else 'All')}") 20 | 21 | print("running flake8...") 22 | if os.system("flake8 --statistics --config .flake8 ."): 23 | exit() 24 | 25 | ret = os.system(f'coverage run --include="$PWD/*" manage.py test {test_module} --settings={setting}') 26 | 27 | if not ret and is_coverage: 28 | os.system('echo "\n----------------------------------------------------------"') 29 | os.system('echo "🌎 Open http://localhost:9000 to check coverage result."') 30 | os.system('echo "✋ Press Ctrl + C to stop serving.\n"') 31 | os.system("coverage html && npx --yes http-server htmlcov -s -p 9000") 32 | -------------------------------------------------------------------------------- /backend/submission/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/submission/__init__.py -------------------------------------------------------------------------------- /backend/submission/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2017-05-09 06:41 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import jsonfield.fields 7 | import utils.models 8 | import utils.shortcuts 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | initial = True 14 | 15 | dependencies = [ 16 | ] 17 | 18 | operations = [ 19 | migrations.CreateModel( 20 | name='Submission', 21 | fields=[ 22 | ('id', models.CharField(db_index=True, default=utils.shortcuts.rand_str, max_length=32, primary_key=True, serialize=False)), 23 | ('contest_id', models.IntegerField(db_index=True, null=True)), 24 | ('problem_id', models.IntegerField(db_index=True)), 25 | ('created_time', models.DateTimeField(auto_now_add=True)), 26 | ('user_id', models.IntegerField(db_index=True)), 27 | ('code', utils.models.RichTextField()), 28 | ('result', models.IntegerField(default=6)), 29 | ('info', jsonfield.fields.JSONField(default={})), 30 | ('language', models.CharField(max_length=20)), 31 | ('shared', models.BooleanField(default=False)), 32 | ('accepted_time', models.IntegerField(blank=True, null=True)), 33 | ('accepted_info', jsonfield.fields.JSONField(default={})), 34 | ], 35 | options={ 36 | 'db_table': 'submission', 37 | }, 38 | ), 39 | ] 40 | -------------------------------------------------------------------------------- /backend/submission/migrations/0002_auto_20170509_1203.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2017-05-09 12:03 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('submission', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='submission', 17 | name='code', 18 | field=models.TextField(), 19 | ), 20 | migrations.RenameField( 21 | model_name='submission', 22 | old_name='accepted_info', 23 | new_name='statistic_info', 24 | ), 25 | migrations.RemoveField( 26 | model_name='submission', 27 | name='accepted_time', 28 | ), 29 | migrations.RenameField( 30 | model_name='submission', 31 | old_name='created_time', 32 | new_name='create_time', 33 | ), 34 | migrations.AlterModelOptions( 35 | name='submission', 36 | options={'ordering': ('-create_time',)}, 37 | ) 38 | ] 39 | -------------------------------------------------------------------------------- /backend/submission/migrations/0005_submission_username.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-08-26 03:47 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('submission', '0002_auto_20170509_1203'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='submission', 17 | name='username', 18 | field=models.CharField(default="", max_length=30), 19 | preserve_default=False, 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /backend/submission/migrations/0006_auto_20170830_1154.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-08-30 11:54 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('submission', '0005_submission_username'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='submission', 17 | name='result', 18 | field=models.IntegerField(db_index=True, default=6), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /backend/submission/migrations/0007_auto_20170923_1318.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-09-23 13:18 3 | from __future__ import unicode_literals 4 | 5 | import django.contrib.postgres.fields.jsonb 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ('submission', '0006_auto_20170830_1154'), 14 | ] 15 | 16 | operations = [ 17 | migrations.AlterField( 18 | model_name='submission', 19 | name='contest_id', 20 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='contest.Contest'), 21 | ), 22 | migrations.AlterField( 23 | model_name='submission', 24 | name='problem_id', 25 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='problem.Problem'), 26 | ), 27 | migrations.RenameField( 28 | model_name='submission', 29 | old_name='contest_id', 30 | new_name='contest', 31 | ), 32 | migrations.RenameField( 33 | model_name='submission', 34 | old_name='problem_id', 35 | new_name='problem', 36 | ), 37 | migrations.AlterField( 38 | model_name='submission', 39 | name='info', 40 | field=django.contrib.postgres.fields.jsonb.JSONField(default=dict), 41 | ), 42 | migrations.AlterField( 43 | model_name='submission', 44 | name='statistic_info', 45 | field=django.contrib.postgres.fields.jsonb.JSONField(default=dict), 46 | ), 47 | ] 48 | -------------------------------------------------------------------------------- /backend/submission/migrations/0008_submission_ip.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.4 on 2017-11-10 06:57 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('submission', '0007_auto_20170923_1318'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='submission', 17 | name='ip', 18 | field=models.CharField(blank=True, max_length=32, null=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /backend/submission/migrations/0009_delete_user_output.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations 5 | 6 | 7 | def delete_user_output(apps, schema_editor): 8 | Submission = apps.get_model("submission", "Submission") 9 | for item in Submission.objects.all(): 10 | if "data" in item.info and isinstance(item.info["data"], list): 11 | for index in range(len(item.info["data"])): 12 | item.info["data"][index]["output"] = "" 13 | item.save() 14 | 15 | 16 | class Migration(migrations.Migration): 17 | 18 | dependencies = [ 19 | ('submission', '0008_submission_ip'), 20 | ] 21 | 22 | operations = [ 23 | migrations.RunPython(delete_user_output, reverse_code=migrations.RunPython.noop) 24 | ] 25 | -------------------------------------------------------------------------------- /backend/submission/migrations/0011_fix_submission_number.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations 5 | 6 | 7 | def fix_rejudge_bugs(apps, schema_editor): 8 | Submission = apps.get_model("submission", "Submission") 9 | User = apps.get_model("account", "User") 10 | 11 | for user in User.objects.all(): 12 | submissions = Submission.objects.filter(user_id=user.id, contest__isnull=True) 13 | profile = user.userprofile 14 | profile.submission_number = submissions.count() 15 | profile.accepted_number = submissions.filter(result=0).count() 16 | profile.save(update_fields=["submission_number", "accepted_number"]) 17 | 18 | 19 | class Migration(migrations.Migration): 20 | dependencies = [ 21 | ('submission', '0009_delete_user_output'), 22 | ('problem', '0010_problem_spj_compile_ok'), 23 | ] 24 | 25 | operations = [ 26 | migrations.RunPython(fix_rejudge_bugs, reverse_code=migrations.RunPython.noop) 27 | ] 28 | -------------------------------------------------------------------------------- /backend/submission/migrations/0012_auto_20180501_0436.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.3 on 2018-05-01 04:36 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import utils.shortcuts 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('submission', '0011_fix_submission_number'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='submission', 18 | name='id', 19 | field=models.TextField(db_index=True, default=utils.shortcuts.rand_str, primary_key=True, serialize=False), 20 | ), 21 | migrations.AlterField( 22 | model_name='submission', 23 | name='ip', 24 | field=models.TextField(null=True), 25 | ), 26 | migrations.AlterField( 27 | model_name='submission', 28 | name='language', 29 | field=models.TextField(), 30 | ), 31 | migrations.AlterField( 32 | model_name='submission', 33 | name='username', 34 | field=models.TextField(), 35 | ), 36 | ] 37 | -------------------------------------------------------------------------------- /backend/submission/migrations/0013_auto_20210730_2244.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.24 on 2021-07-30 13:44 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('assignment', '0001_initial'), 11 | ('submission', '0012_auto_20180501_0436'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='submission', 17 | name='assignment', 18 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='assignment.Assignment'), 19 | ), 20 | migrations.AddField( 21 | model_name='submission', 22 | name='score', 23 | field=models.IntegerField(null=True), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /backend/submission/migrations/0014_auto_20210802_2115.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.24 on 2021-08-02 12:15 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('submission', '0013_auto_20210730_2244'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='submission', 16 | name='assignment', 17 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='submissions', to='assignment.Assignment'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /backend/submission/migrations/0015_remove_submission_score.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.24 on 2021-08-08 08:09 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('submission', '0014_auto_20210802_2115'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='submission', 15 | name='score', 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /backend/submission/migrations/0016_auto_20211226_1458.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.5 on 2021-12-26 05:58 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('submission', '0015_remove_submission_score'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='submission', 15 | name='info', 16 | field=models.JSONField(default=dict), 17 | ), 18 | migrations.AlterField( 19 | model_name='submission', 20 | name='statistic_info', 21 | field=models.JSONField(default=dict), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /backend/submission/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/submission/migrations/__init__.py -------------------------------------------------------------------------------- /backend/submission/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from .views import (SubmissionAPI, SubmissionListAPI, ContestSubmissionListAPI, AssignmentSubmissionListAPI, 4 | AssignmentSubmissionListProfessorAPI, SubmissionExistsAPI, EditSubmissionScoreAPI) 5 | 6 | urlpatterns = [ 7 | path("submission/", SubmissionAPI.as_view(), name="submission_api"), 8 | path("submissions/", SubmissionListAPI.as_view(), name="submission_list_api"), 9 | path("submission_exists/", SubmissionExistsAPI.as_view(), name="submission_exists"), 10 | path("contest_submissions/", ContestSubmissionListAPI.as_view(), name="contest_submission_list_api"), 11 | path("assignment_submissions/", AssignmentSubmissionListAPI.as_view(), name="assignment_submission_list_api"), 12 | path("assignment_submissions_professor/", AssignmentSubmissionListProfessorAPI.as_view(), name="assignment_submission_list_professor_api"), 13 | path("edit_submission_score/", EditSubmissionScoreAPI.as_view(), name="edit_submission_score_api"), 14 | ] 15 | -------------------------------------------------------------------------------- /backend/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/utils/__init__.py -------------------------------------------------------------------------------- /backend/utils/api/__init__.py: -------------------------------------------------------------------------------- 1 | from ._serializers import * # NOQA 2 | from .api import * # NOQA 3 | -------------------------------------------------------------------------------- /backend/utils/api/_serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | 4 | class UsernameSerializer(serializers.Serializer): 5 | id = serializers.IntegerField() 6 | username = serializers.CharField() 7 | real_name = serializers.SerializerMethodField() 8 | 9 | def __init__(self, *args, **kwargs): 10 | self.need_real_name = kwargs.pop("need_real_name", False) 11 | super().__init__(*args, **kwargs) 12 | 13 | def get_real_name(self, obj) -> str: 14 | return obj.userprofile.real_name if self.need_real_name else None 15 | -------------------------------------------------------------------------------- /backend/utils/cache.py: -------------------------------------------------------------------------------- 1 | from django.core.cache import cache, caches # noqa 2 | from django.conf import settings # noqa 3 | 4 | from django_redis.cache import RedisCache 5 | from django_redis.client.default import DefaultClient 6 | 7 | 8 | class MyRedisClient(DefaultClient): 9 | def __getattr__(self, item): 10 | client = self.get_client(write=True) 11 | return getattr(client, item) 12 | 13 | def redis_incr(self, key, count=1): 14 | """ 15 | Django's default incr will throw an exception when the key does not exist 16 | """ 17 | client = self.get_client(write=True) 18 | return client.incr(key, count) 19 | 20 | 21 | class MyRedisCache(RedisCache): 22 | def __init__(self, server, params): 23 | super().__init__(server, params) 24 | self._client_cls = MyRedisClient 25 | 26 | def __getattr__(self, item): 27 | return getattr(self.client, item) 28 | -------------------------------------------------------------------------------- /backend/utils/captcha/Menlo.ttc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/utils/captcha/Menlo.ttc -------------------------------------------------------------------------------- /backend/utils/captcha/timesbi.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/utils/captcha/timesbi.ttf -------------------------------------------------------------------------------- /backend/utils/captcha/views.py: -------------------------------------------------------------------------------- 1 | from . import Captcha 2 | from ..api import APIView 3 | from ..shortcuts import img2base64 4 | 5 | 6 | class CaptchaAPIView(APIView): 7 | def get(self, request): 8 | return self.success(img2base64(Captcha(request).get())) 9 | -------------------------------------------------------------------------------- /backend/utils/constants.py: -------------------------------------------------------------------------------- 1 | class Choices: 2 | @classmethod 3 | def choices(cls): 4 | d = cls.__dict__ 5 | return [d[item] for item in d.keys() if not item.startswith("__")] 6 | 7 | 8 | class ContestType: 9 | PUBLIC_CONTEST = "Public" 10 | PASSWORD_PROTECTED_CONTEST = "Password Protected" 11 | 12 | 13 | class ContestStatus: 14 | CONTEST_NOT_START = "1" 15 | CONTEST_ENDED = "-1" 16 | CONTEST_UNDERWAY = "0" 17 | 18 | 19 | class ContestRuleType(Choices): 20 | ACM = "ACM" 21 | OI = "OI" 22 | 23 | 24 | class AssignmentStatus: 25 | ASSIGNMENT_NOT_START = "1" 26 | ASSIGNMENT_ENDED = "-1" 27 | ASSIGNMENT_UNDERWAY = "0" 28 | 29 | 30 | class CacheKey: 31 | waiting_queue = "waiting_queue" 32 | contest_rank_cache = "contest_rank_cache" 33 | website_config = "website_config" 34 | auth_token_cache = "email_auth/token" 35 | auth_email_cache = "email_auth/email" 36 | 37 | 38 | class Difficulty(Choices): 39 | Level1 = "Level1" 40 | Level2 = "Level2" 41 | Level3 = "Level3" 42 | Level4 = "Level4" 43 | Level5 = "Level5" 44 | Level6 = "Level6" 45 | Level7 = "Level7" 46 | 47 | 48 | CONTEST_PASSWORD_SESSION_KEY = "contest_password" 49 | -------------------------------------------------------------------------------- /backend/utils/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/utils/management/__init__.py -------------------------------------------------------------------------------- /backend/utils/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/backend/utils/management/commands/__init__.py -------------------------------------------------------------------------------- /backend/utils/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from utils.xss_filter import XSSHtml 4 | 5 | 6 | class RichTextField(models.TextField): 7 | def get_prep_value(self, value): 8 | with XSSHtml() as parser: 9 | return parser.clean(value or "") 10 | -------------------------------------------------------------------------------- /backend/utils/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | from options.options import SysOptions 4 | 5 | 6 | class InvalidLanguage(serializers.ValidationError): 7 | def __init__(self, name): 8 | super().__init__(detail=f"{name} is not a valid language") 9 | 10 | 11 | class LanguageNameChoiceField(serializers.CharField): 12 | def to_internal_value(self, data): 13 | data = super().to_internal_value(data) 14 | if data and data not in SysOptions.language_names: 15 | raise InvalidLanguage(data) 16 | return data 17 | 18 | 19 | class SPJLanguageNameChoiceField(serializers.CharField): 20 | def to_internal_value(self, data): 21 | data = super().to_internal_value(data) 22 | if data and data not in SysOptions.spj_language_names: 23 | raise InvalidLanguage(data) 24 | return data 25 | 26 | 27 | class LanguageNameMultiChoiceField(serializers.ListField): 28 | def to_internal_value(self, data): 29 | data = super().to_internal_value(data) 30 | for item in data: 31 | if item not in SysOptions.language_names: 32 | raise InvalidLanguage(item) 33 | return data 34 | 35 | 36 | class SPJLanguageNameMultiChoiceField(serializers.ListField): 37 | def to_internal_value(self, data): 38 | data = super().to_internal_value(data) 39 | for item in data: 40 | if item not in SysOptions.spj_language_names: 41 | raise InvalidLanguage(item) 42 | return data 43 | -------------------------------------------------------------------------------- /backend/utils/tasks.py: -------------------------------------------------------------------------------- 1 | import os 2 | import dramatiq 3 | 4 | from utils.shortcuts import DRAMATIQ_WORKER_ARGS 5 | 6 | 7 | @dramatiq.actor(**DRAMATIQ_WORKER_ARGS()) 8 | def delete_files(*args): 9 | for item in args: 10 | try: 11 | os.remove(item) 12 | except Exception: 13 | pass 14 | -------------------------------------------------------------------------------- /backend/utils/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from .views import SimditorImageUploadAPIView, SimditorFileUploadAPIView 4 | 5 | urlpatterns = [ 6 | path("upload_image/", SimditorImageUploadAPIView.as_view(), name="upload_image"), 7 | path("upload_file/", SimditorFileUploadAPIView.as_view(), name="upload_file") 8 | ] 9 | -------------------------------------------------------------------------------- /db_backup.sh: -------------------------------------------------------------------------------- 1 | docker exec -it oj-postgres pg_dumpall -c -U onlinejudge > db_backup_`date +%Y_%m_%d"_"%H_%M_%S`.sql 2 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | services: 3 | oj-redis: 4 | image: redis:4.0-alpine 5 | container_name: oj-redis 6 | restart: always 7 | volumes: 8 | - ./data/redis:/data 9 | 10 | oj-postgres: 11 | image: postgres:10-alpine 12 | container_name: oj-postgres 13 | restart: always 14 | volumes: 15 | - ./data/postgres:/var/lib/postgresql/data 16 | environment: 17 | - POSTGRES_DB=onlinejudge 18 | - POSTGRES_USER=onlinejudge 19 | - POSTGRES_PASSWORD=onlinejudge 20 | 21 | judge-server: 22 | image: skkunpc/judge-server 23 | container_name: judge-server 24 | restart: always 25 | read_only: true 26 | cap_drop: 27 | - SETPCAP 28 | - MKNOD 29 | - NET_BIND_SERVICE 30 | - SYS_CHROOT 31 | - SETFCAP 32 | - FSETID 33 | tmpfs: 34 | - /tmp 35 | volumes: 36 | - ./data/backend/test_case:/test_case:ro 37 | - ./data/judge-server/log:/log 38 | - ./data/judge-server/run:/judger 39 | environment: 40 | - SERVICE_URL=http://judge-server:8080 41 | - BACKEND_URL=http://coding-platform:8000/api/judge_server_heartbeat/ 42 | - TOKEN=CHANGE_THIS 43 | # - judger_debug=1 44 | 45 | coding-platform: 46 | image: ghcr.io/skkuding/coding-platform 47 | container_name: coding-platform 48 | restart: always 49 | depends_on: 50 | - oj-redis 51 | - oj-postgres 52 | - judge-server 53 | volumes: 54 | - ./data/backend:/data 55 | environment: 56 | - POSTGRES_DB=onlinejudge 57 | - POSTGRES_USER=onlinejudge 58 | - POSTGRES_PASSWORD=onlinejudge 59 | - JUDGE_SERVER_TOKEN=CHANGE_THIS 60 | ports: 61 | - "0.0.0.0:80:8000" 62 | - "0.0.0.0:443:1443" 63 | -------------------------------------------------------------------------------- /frontend/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /frontend/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 2 4 | end_of_line = lf 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | -------------------------------------------------------------------------------- /frontend/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | extends: [ 7 | 'plugin:vue/essential', 8 | '@vue/standard', 9 | 'plugin:cypress/recommended' 10 | ], 11 | parserOptions: { 12 | parser: '@babel/eslint-parser' 13 | }, 14 | rules: { 15 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 16 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 17 | 'vue/no-parsing-error': ['error', { 18 | 'x-invalid-end-tag': false 19 | }] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /frontend/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /frontend/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "https://localhost" 3 | } 4 | -------------------------------------------------------------------------------- /frontend/cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } 6 | -------------------------------------------------------------------------------- /frontend/cypress/integration/oj/problem/problem.spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // describe -> context -> it 4 | context('When user tries to submit a problem (not in contest)', () => { 5 | beforeEach(() => { 6 | cy.loginSuperAdmin() 7 | cy.visit('problem') 8 | }) 9 | // TODO: make it independent from admin_problem_spec (anti-pattern) 10 | it('Submit Code', () => { 11 | cy.fixture('problem-example').then((problemExample) => { 12 | cy.get('tbody > tr > .problem-title-field').contains(problemExample.problem.title).click() 13 | cy.get('[data-cy="toggle-language"] > .dropdown-toggle-split').click() 14 | cy.get('[data-cy="select-langauge"]').contains(problemExample.solution.language).click() 15 | cy.get('.CodeMirror-code').click().type(problemExample.solution.code, { delay: 0 }) 16 | cy.get('[data-cy="button-submit"]').click() 17 | }) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /frontend/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | /// 2 | // *********************************************************** 3 | // This example plugins/index.js can be used to load plugins 4 | // 5 | // You can change the location of this file or turn off loading 6 | // the plugins file with the 'pluginsFile' configuration option. 7 | // 8 | // You can read more here: 9 | // https://on.cypress.io/plugins-guide 10 | // *********************************************************** 11 | 12 | // This function is called when a project is opened or re-opened (e.g. due to 13 | // the project's config changing) 14 | 15 | /** 16 | * @type {Cypress.PluginConfig} 17 | */ 18 | // eslint-disable-next-line no-unused-vars 19 | module.exports = (on, config) => { 20 | // `on` is used to hook into various events Cypress emits 21 | // `config` is the resolved Cypress config 22 | } 23 | -------------------------------------------------------------------------------- /frontend/cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | Cypress.Commands.add('loginSuperAdmin', () => { 2 | cy.visit('') 3 | cy.getCookie('csrftoken') 4 | .should('exist') 5 | .then((csrftoken) => { 6 | cy.request({ 7 | method: 'post', 8 | url: 'api/login/', 9 | body: { 10 | username: 'root', 11 | password: 'rootroot' 12 | }, 13 | headers: { 14 | 'X-CSRFToken': csrftoken.value 15 | } 16 | }) 17 | }) 18 | const getStore = () => cy.window().its('app.$store') 19 | getStore().then(store => { 20 | store.dispatch('getProfile') 21 | }) 22 | }) 23 | 24 | // TODO: Register, Login by general user 25 | -------------------------------------------------------------------------------- /frontend/cypress/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | import './commands' 17 | 18 | Cypress.Cookies.defaults({ 19 | preserve: ['csrftoken', 'session_id'] 20 | }) 21 | 22 | Cypress.Commands.overwrite('request', (originalFn, ...args) => { 23 | let options = {} 24 | if (typeof args[0] === 'object' && args[0] !== null) { 25 | options = args[0] 26 | } else if (args.length === 1) { 27 | [options.url] = args 28 | } else if (args.length === 2) { 29 | [options.method, options.url] = args 30 | } else if (args.length === 3) { 31 | [options.method, options.url, options.body] = args 32 | } 33 | cy.getCookie('csrftoken') 34 | .should('exist') 35 | .then((csrftoken) => { 36 | const defaults = { 37 | headers: { 38 | 'X-CSRFToken': csrftoken.value 39 | } 40 | } 41 | return originalFn({ ...defaults, ...options, ...{ headers: { ...defaults.headers, ...options.headers } } }) 42 | }) 43 | }) 44 | -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {} 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /frontend/public/cache.dll.json: -------------------------------------------------------------------------------- 1 | ["dll.c9dc8146.dll.js"] -------------------------------------------------------------------------------- /frontend/src/assets/Cup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/frontend/src/assets/Cup.png -------------------------------------------------------------------------------- /frontend/src/assets/icons/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/link.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/mail.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/logos/codingPlatformLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/frontend/src/assets/logos/codingPlatformLogo.png -------------------------------------------------------------------------------- /frontend/src/assets/logos/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/frontend/src/assets/logos/logo.png -------------------------------------------------------------------------------- /frontend/src/assets/logos/signature.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/frontend/src/assets/logos/signature.png -------------------------------------------------------------------------------- /frontend/src/assets/standing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/frontend/src/assets/standing.png -------------------------------------------------------------------------------- /frontend/src/fonts/Manrope-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/frontend/src/fonts/Manrope-Bold.ttf -------------------------------------------------------------------------------- /frontend/src/fonts/Manrope-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/frontend/src/fonts/Manrope-ExtraBold.ttf -------------------------------------------------------------------------------- /frontend/src/fonts/Manrope-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/frontend/src/fonts/Manrope-ExtraLight.ttf -------------------------------------------------------------------------------- /frontend/src/fonts/Manrope-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/frontend/src/fonts/Manrope-Light.ttf -------------------------------------------------------------------------------- /frontend/src/fonts/Manrope-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/frontend/src/fonts/Manrope-Medium.ttf -------------------------------------------------------------------------------- /frontend/src/fonts/Manrope-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/frontend/src/fonts/Manrope-Regular.ttf -------------------------------------------------------------------------------- /frontend/src/fonts/Manrope-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skkuding/skku-coding-platform/ffc2aec1f38122e9861593b69dd3d45d443d7e47/frontend/src/fonts/Manrope-SemiBold.ttf -------------------------------------------------------------------------------- /frontend/src/pages/admin/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | 31 | -------------------------------------------------------------------------------- /frontend/src/pages/admin/components/KatexEditor.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 34 | 35 | 40 | -------------------------------------------------------------------------------- /frontend/src/pages/admin/components/btn/Cancel.vue: -------------------------------------------------------------------------------- 1 | 6 | 11 | 16 | -------------------------------------------------------------------------------- /frontend/src/pages/admin/components/btn/IconBtn.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 29 | -------------------------------------------------------------------------------- /frontend/src/pages/admin/components/btn/Save.vue: -------------------------------------------------------------------------------- 1 | 6 | 11 | 16 | -------------------------------------------------------------------------------- /frontend/src/pages/admin/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | OnlineJudge 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /frontend/src/pages/admin/views/index.js: -------------------------------------------------------------------------------- 1 | import Dashboard from './general/Dashboard.vue' 2 | import Announcement from './general/Announcement.vue' 3 | import User from './general/User.vue' 4 | import Conf from './general/Conf.vue' 5 | import JudgeServer from './general/JudgeServer.vue' 6 | import PruneTestCase from './general/PruneTestCase.vue' 7 | import Problem from './problem/Problem.vue' 8 | import ProblemList from './problem/ProblemList.vue' 9 | import ProblemSet from './problem/ProblemSet.vue' 10 | import ContestList from './contest/ContestList.vue' 11 | import Contest from './contest/Contest.vue' 12 | import Login from './general/Login.vue' 13 | import Home from './Home.vue' 14 | import Banner from './general/Banner.vue' 15 | 16 | export { 17 | Announcement, User, Conf, JudgeServer, Problem, ProblemList, ProblemSet, 18 | Contest, ContestList, Login, Home, PruneTestCase, Dashboard, Banner 19 | } 20 | -------------------------------------------------------------------------------- /frontend/src/pages/oj/components/Banner.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 43 | 44 | 49 | -------------------------------------------------------------------------------- /frontend/src/pages/oj/components/ColorRoundButton.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 35 | 36 | 44 | -------------------------------------------------------------------------------- /frontend/src/pages/oj/components/Highlight.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 38 | 39 | 49 | -------------------------------------------------------------------------------- /frontend/src/pages/oj/components/PageTitle.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 21 | -------------------------------------------------------------------------------- /frontend/src/pages/oj/components/PageTop.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 25 | 26 | 39 | -------------------------------------------------------------------------------- /frontend/src/pages/oj/components/ShadowRoundButton.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 36 | 37 | 45 | -------------------------------------------------------------------------------- /frontend/src/pages/oj/components/mixins/emitter.js: -------------------------------------------------------------------------------- 1 | function broadcast (componentName, eventName, params) { 2 | this.$children.forEach(child => { 3 | const name = child.$options.name 4 | 5 | if (name === componentName) { 6 | child.$emit.apply(child, [eventName].concat(params)) 7 | } else { 8 | // todo 如果 params 是空数组,接收到的会是 undefined 9 | broadcast.apply(child, [componentName, eventName].concat([params])) 10 | } 11 | }) 12 | } 13 | 14 | export default { 15 | methods: { 16 | dispatch (componentName, eventName, params) { 17 | let parent = this.$parent || this.$root 18 | let name = parent.$options.name 19 | 20 | while (parent && (!name || name !== componentName)) { 21 | parent = parent.$parent 22 | 23 | if (parent) { 24 | name = parent.$options.name 25 | } 26 | } 27 | if (parent) { 28 | parent.$emit.apply(parent, [eventName].concat(params)) 29 | } 30 | }, 31 | broadcast (componentName, eventName, params) { 32 | broadcast.call(this, componentName, eventName, params) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /frontend/src/pages/oj/components/mixins/form.js: -------------------------------------------------------------------------------- 1 | import api from '@oj/api' 2 | 3 | export default { 4 | data () { 5 | return { 6 | captchaSrc: '' 7 | } 8 | }, 9 | methods: { 10 | async validateForm (formName) { 11 | this.$refs[formName].validate((valid) => { 12 | if (!valid) { 13 | this.$error('please validate the error fields') 14 | } else { 15 | return valid 16 | } 17 | }) 18 | }, 19 | async getCaptchaSrc () { 20 | try { 21 | const res = await api.getCaptcha() 22 | this.captchaSrc = res.data.data 23 | } catch (err) { 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /frontend/src/pages/oj/components/mixins/index.js: -------------------------------------------------------------------------------- 1 | import Emitter from './emitter' 2 | import ProblemMixin from './problem' 3 | import FormMixin from './form' 4 | 5 | export { Emitter, ProblemMixin, FormMixin } 6 | -------------------------------------------------------------------------------- /frontend/src/pages/oj/components/mixins/problem.js: -------------------------------------------------------------------------------- 1 | import utils from '@/utils/utils' 2 | 3 | export default { 4 | data () { 5 | return { 6 | statusColumn: false 7 | } 8 | }, 9 | methods: { 10 | getACRate (ACCount, TotalCount) { 11 | return utils.getACRate(ACCount, TotalCount) 12 | }, 13 | addStatusColumn (tableColumns, dataProblems) { 14 | // 已添加过直接返回 15 | if (this.statusColumn) return 16 | // 只在有做题记录时才添加column 17 | const needAdd = dataProblems.some((item, index) => { 18 | if (item.my_status !== null && item.my_status !== undefined) { 19 | return true 20 | } 21 | return false 22 | }) 23 | if (!needAdd) { 24 | return 25 | } 26 | tableColumns.splice(0, 0, { 27 | width: 60, 28 | title: ' ', 29 | render: (h, params) => { 30 | const status = params.row.my_status 31 | if (status === null || status === undefined) { 32 | return undefined 33 | } 34 | return h('Icon', { 35 | props: { 36 | type: status === 0 ? 'checkmark-round' : 'minus-round', 37 | size: '16' 38 | }, 39 | style: { 40 | color: status === 0 ? '#19be6b' : '#ed3f14' 41 | } 42 | }) 43 | } 44 | }) 45 | this.statusColumn = true 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /frontend/src/pages/oj/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | OnlineJudge 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 19 | 20 | 21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | 33 |
34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /frontend/src/pages/oj/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | import routes from './routes' 4 | import storage from '@/utils/storage' 5 | import { STORAGE_KEY } from '@/utils/constants' 6 | import { sync } from 'vuex-router-sync' 7 | import store, { types } from '@/store' 8 | 9 | Vue.use(VueRouter) 10 | 11 | const router = new VueRouter({ 12 | mode: 'history', 13 | scrollBehavior (to, from, savedPosition) { 14 | if (savedPosition) { 15 | return savedPosition 16 | } else { 17 | return { x: 0, y: 0 } 18 | } 19 | }, 20 | routes 21 | }) 22 | 23 | // 全局身份确认 24 | router.beforeEach((to, from, next) => { 25 | if (to.matched.some(record => record.meta.requiresAuth)) { 26 | if (!storage.get(STORAGE_KEY.AUTHED)) { 27 | Vue.prototype.$error('Please login first') 28 | store.commit(types.CHANGE_MODAL_STATUS, { mode: 'login', visible: true }) 29 | next({ 30 | name: 'home' 31 | }) 32 | } else { 33 | next() 34 | } 35 | } else { 36 | next() 37 | } 38 | }) 39 | 40 | sync(store, router) 41 | 42 | export default router 43 | -------------------------------------------------------------------------------- /frontend/src/pages/oj/views/user/EmailAuth.vue: -------------------------------------------------------------------------------- 1 | 23 | -------------------------------------------------------------------------------- /frontend/src/pages/oj/views/user/Logout.vue: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /frontend/src/pages/professor/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | 31 | -------------------------------------------------------------------------------- /frontend/src/pages/professor/components/KatexEditor.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 34 | 35 | 40 | -------------------------------------------------------------------------------- /frontend/src/pages/professor/components/btn/Cancel.vue: -------------------------------------------------------------------------------- 1 | 6 | 11 | 16 | -------------------------------------------------------------------------------- /frontend/src/pages/professor/components/btn/IconBtn.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 29 | -------------------------------------------------------------------------------- /frontend/src/pages/professor/components/btn/Save.vue: -------------------------------------------------------------------------------- 1 | 6 | 11 | 16 | -------------------------------------------------------------------------------- /frontend/src/pages/professor/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SKKU Coding platform professor 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /frontend/src/pages/professor/views/index.js: -------------------------------------------------------------------------------- 1 | import Dashboard from './general/Dashboard.vue' 2 | import CourseDashboard from './general/CourseDashboard.vue' 3 | import Problem from './problem/Problem.vue' 4 | import AssignmentList from './assignment/AssignmentList.vue' 5 | import Login from './general/Login.vue' 6 | import Home from './Home.vue' 7 | import QnA from './qna/QnA.vue' 8 | import ProblemGrade from './problem/ProblemGrade.vue' 9 | import CourseBookmark from './general/CourseBookmark.vue' 10 | 11 | export { 12 | Dashboard, CourseDashboard, Problem, AssignmentList, Login, Home, QnA, ProblemGrade, CourseBookmark 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/pages/professor/views/qna/QnA.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 55 | 56 | 58 | -------------------------------------------------------------------------------- /frontend/src/plugins/highlight.js: -------------------------------------------------------------------------------- 1 | import hljs from 'highlight.js/lib/highlight' 2 | // import cpp from 'highlight.js/lib/languages/cpp' 3 | // import python from 'highlight.js/lib/languages/python' 4 | // import java from 'highlight.js/lib/languages/java' 5 | import 'highlight.js/styles/atom-one-light.css' 6 | // hljs.registerLanguage('cpp', cpp) 7 | // hljs.registerLanguage('java', java) 8 | // hljs.registerLanguage('python', python) 9 | export default { 10 | install (Vue, options) { 11 | Vue.directive('highlight', { 12 | deep: true, 13 | bind: function (el, binding) { 14 | // on first bind, highlight all targets 15 | Array.from(el.querySelectorAll('code')).forEach((target) => { 16 | // if a value is directly assigned to the directive, use this 17 | // instead of the element content. 18 | if (binding.value) { 19 | target.textContent = binding.value 20 | } 21 | hljs.highlightBlock(target) 22 | }) 23 | }, 24 | componentUpdated: function (el, binding) { 25 | // after an update, re-fill the content and then highlight 26 | Array.from(el.querySelectorAll('code')).forEach((target) => { 27 | if (binding.value) { 28 | target.textContent = binding.value 29 | } 30 | hljs.highlightBlock(target) 31 | }) 32 | } 33 | }) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /frontend/src/store/modules/group.js: -------------------------------------------------------------------------------- 1 | import types from '../types' 2 | import api from '@oj/api' 3 | 4 | const state = { 5 | groups: { 6 | admin_groups: [], 7 | groups: [], 8 | other_groups: [] 9 | } 10 | } 11 | 12 | const getters = { 13 | groups: state => state.groups.groups || [], 14 | adminGroups: state => state.groups.admin_groups || [], 15 | otherGroups: state => state.groups.other_groups || [] 16 | } 17 | 18 | const mutations = { 19 | [types.CHANGE_GROUP_LIST] (state, payload) { 20 | state.groups = payload.groups 21 | } 22 | } 23 | 24 | const actions = { 25 | async getGroupList ({ commit, rootState }) { 26 | try { 27 | const res = await api.getGroupList() 28 | commit(types.CHANGE_GROUP_LIST, { groups: res.data.data }) 29 | return res 30 | } catch (err) { 31 | commit(types.CHANGE_GROUP_LIST, { groups: [] }) 32 | throw err 33 | } 34 | } 35 | } 36 | 37 | export default { 38 | state, 39 | mutations, 40 | getters, 41 | actions 42 | } 43 | -------------------------------------------------------------------------------- /frontend/src/store/modules/user.js: -------------------------------------------------------------------------------- 1 | import types from '../types' 2 | import api from '@oj/api' 3 | import storage from '@/utils/storage' 4 | import { STORAGE_KEY, USER_TYPE, PROBLEM_PERMISSION } from '@/utils/constants' 5 | 6 | const state = { 7 | profile: {} 8 | } 9 | 10 | const getters = { 11 | user: state => state.profile.user || {}, 12 | profile: state => state.profile, 13 | isAuthenticated: (state, getters) => { 14 | return !!getters.user.id 15 | }, 16 | isAdminRole: (state, getters) => { 17 | return getters.user.admin_type === USER_TYPE.ADMIN || 18 | getters.user.admin_type === USER_TYPE.SUPER_ADMIN 19 | }, 20 | isSuperAdmin: (state, getters) => { 21 | return getters.user.admin_type === USER_TYPE.SUPER_ADMIN 22 | }, 23 | hasProblemPermission: (state, getters) => { 24 | return getters.user.problem_permission !== PROBLEM_PERMISSION.NONE 25 | } 26 | } 27 | 28 | const mutations = { 29 | [types.CHANGE_PROFILE] (state, { profile }) { 30 | state.profile = profile 31 | storage.set(STORAGE_KEY.AUTHED, !!profile.user) 32 | } 33 | } 34 | 35 | const actions = { 36 | async getProfile ({ commit }) { 37 | try { 38 | const res = await api.getUserInfo() 39 | commit(types.CHANGE_PROFILE, { 40 | profile: res.data.data || {} 41 | }) 42 | } catch (err) { 43 | } 44 | }, 45 | clearProfile ({ commit }) { 46 | commit(types.CHANGE_PROFILE, { 47 | profile: {} 48 | }) 49 | // storage.clear() 50 | for (const key in localStorage) { 51 | if (key.indexOf('problemCode') === -1) { 52 | storage.remove(key) 53 | } 54 | } 55 | } 56 | } 57 | 58 | export default { 59 | state, 60 | getters, 61 | actions, 62 | mutations 63 | } 64 | -------------------------------------------------------------------------------- /frontend/src/store/types.js: -------------------------------------------------------------------------------- 1 | function keyMirror (obj) { 2 | if (obj instanceof Object) { 3 | const _obj = Object.assign({}, obj) 4 | const _keyArray = Object.keys(obj) 5 | _keyArray.forEach(key => { 6 | _obj[key] = key 7 | }) 8 | return _obj 9 | } 10 | } 11 | 12 | export default keyMirror({ 13 | CHANGE_PROFILE: null, 14 | CHANGE_MODAL_STATUS: null, 15 | UPDATE_WEBSITE_CONF: null, 16 | 17 | NOW: null, 18 | NOW_ADD_1S: null, 19 | CHANGE_CONTEST: null, 20 | CHANGE_CONTEST_PROBLEMS: null, 21 | CHANGE_CONTEST_ITEM_VISIBLE: null, 22 | CHANGE_RANK_FORCE_UPDATE: null, 23 | CHANGE_CONTEST_RANK_LIMIT: null, 24 | CONTEST_ACCESS: null, 25 | CLEAR_CONTEST: null, 26 | 27 | CHANGE_ASSIGNMENT: null, 28 | CHANGE_ASSIGNMENTLIST: null, 29 | CHANGE_ASSIGNMENT_PROBLEMS: null, 30 | 31 | CHANGE_GROUP_LIST: null 32 | }) 33 | -------------------------------------------------------------------------------- /frontend/src/styles/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /frontend/src/styles/tiptapview.scss: -------------------------------------------------------------------------------- 1 | ul, 2 | ol { 3 | padding: 0 1rem; 4 | } 5 | code { 6 | background-color: rgba(#616161, 0.1); 7 | } 8 | pre { 9 | background: #0D0D0D; 10 | color: #FFF; 11 | font-family: 'JetBrainsMono', monospace; 12 | padding: 0.75rem 1rem; 13 | border-radius: 0.5rem; 14 | 15 | code { 16 | color: inherit; 17 | padding: 0; 18 | background: none; 19 | font-size: 0.8rem; 20 | } 21 | } 22 | img { 23 | max-width: 100%; 24 | height: auto; 25 | } 26 | blockquote { 27 | padding-left: 1rem; 28 | border-left: 2px solid rgba(#0D0D0D, 0.1); 29 | } 30 | hr { 31 | border: none; 32 | border-top: 2px solid rgba(#0D0D0D, 0.1); 33 | } -------------------------------------------------------------------------------- /frontend/src/utils/filters.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment' 2 | import utils from './utils' 3 | import time from './time' 4 | 5 | // 友好显示时间 6 | function fromNow (time) { 7 | return moment(time * 3).fromNow() 8 | } 9 | 10 | export default { 11 | submissionMemory: utils.submissionMemoryFormat, 12 | submissionTime: utils.submissionTimeFormat, 13 | localtime: time.utcToLocal, 14 | fromNow: fromNow 15 | } 16 | -------------------------------------------------------------------------------- /frontend/src/utils/storage.js: -------------------------------------------------------------------------------- 1 | const localStorage = window.localStorage 2 | 3 | export default { 4 | name: 'storage', 5 | 6 | /** 7 | * save value(Object) to key 8 | * @param {string} key 键 9 | * @param {Object} value 值 10 | */ 11 | set (key, value) { 12 | localStorage.setItem(key, JSON.stringify(value)) 13 | }, 14 | 15 | /** 16 | * get value(Object) by key 17 | * @param {string} key 键 18 | * @return {Object} 19 | */ 20 | get (key) { 21 | return JSON.parse(localStorage.getItem(key)) || null 22 | }, 23 | 24 | /** 25 | * remove key from localStorage 26 | * @param {string} key 键 27 | */ 28 | remove (key) { 29 | localStorage.removeItem(key) 30 | }, 31 | /** 32 | * clear all 33 | */ 34 | clear () { 35 | localStorage.clear() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /frontend/src/utils/time.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment' 2 | 3 | // convert utc time to localtime 4 | function utcToLocal (utcDt, format = 'YYYY-M-D HH:mm:ss') { 5 | return moment.utc(utcDt).local().format(format) 6 | } 7 | 8 | // get duration from startTime to endTime, return like 3 days, 2 hours, one year .. 9 | function duration (startTime, endTime) { 10 | const start = moment(startTime) 11 | const end = moment(endTime) 12 | const duration = moment.duration(start.diff(end, 'seconds'), 'seconds') 13 | if (duration.days() !== 0) { 14 | return duration.humanize() 15 | } 16 | return Math.abs(duration.asHours().toFixed(1)) + ' hours' 17 | } 18 | 19 | function secondFormat (seconds) { 20 | const m = moment.duration(seconds, 'seconds') 21 | return Math.floor(m.asHours()) + ':' + m.minutes() + ':' + m.seconds() 22 | } 23 | 24 | export default { 25 | utcToLocal: utcToLocal, 26 | duration: duration, 27 | secondFormat: secondFormat 28 | } 29 | -------------------------------------------------------------------------------- /frontend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | const defaultTheme = require('tailwindcss/defaultTheme') 2 | 3 | module.exports = { 4 | content: ['./index.html', './src/**/*.{vue,js,jsx}'], 5 | darkMode: false, 6 | theme: { 7 | extend: { 8 | fontFamily: { 9 | sans: [ 10 | 'Manrope', 11 | ...defaultTheme.fontFamily.sans 12 | ] 13 | } 14 | }, 15 | colors: { 16 | white: '#ffffff', 17 | green: '#8DC63F', 18 | blue: '#3391E5', 19 | black: '#000000', 20 | transparent: '#0000', 21 | level: { 22 | level1: '#CC99C9', 23 | level2: '#9EC1CF', 24 | level3: '#A1F2C2', 25 | level4: '#B8FF81', 26 | level5: '#F3EC53', 27 | level6: '#FEB144', 28 | level7: '#FF6663' 29 | }, 30 | text: { 31 | title: '#7C7A7B', 32 | content: '#495057' 33 | }, 34 | table: { 35 | header: '#F9F9FA', 36 | hover: '#072B604D', 37 | border: '#e0e2e3' 38 | } 39 | } 40 | }, 41 | plugins: [] 42 | } 43 | --------------------------------------------------------------------------------