├── .dockerignore
├── config
├── .gitignore
└── config.example.json
├── Frontend
├── .gitignore
├── src
│ ├── logo.png
│ ├── carbon.png
│ ├── g_icon.png
│ ├── glaxoj.png
│ ├── util
│ │ ├── logo.png
│ │ ├── UserContext.js
│ │ ├── config.js
│ │ └── quotes.json
│ ├── setupTests.js
│ ├── App.test.js
│ ├── index.js
│ ├── component
│ │ ├── Footer.js
│ │ └── Nav.js
│ ├── tailwind.css
│ ├── page
│ │ ├── NF.js
│ │ ├── Main.js
│ │ ├── Auth.js
│ │ ├── rank.js
│ │ ├── Submit.js
│ │ ├── ShowProblem.js
│ │ ├── AdminUser.js
│ │ ├── Submission.js
│ │ ├── Admin.js
│ │ ├── Status.js
│ │ ├── Problem.js
│ │ ├── User.js
│ │ └── EditProblem.js
│ ├── App.js
│ └── serviceWorker.js
├── public
│ ├── robots.txt
│ ├── favicon.ico
│ ├── logo192.png
│ ├── manifest.json
│ └── index.html
├── postcss.config.js
├── tailwind.config.js
├── package.json
└── README.md
├── controller
├── judge
│ ├── TEST
│ │ └── .gitignore
│ ├── index.js
│ └── Runner.js
├── submission
│ ├── submission.validation.js
│ └── submission.controller.js
├── problem
│ ├── problem.validation.js
│ └── problem.controller.js
└── user
│ └── user.controller.js
├── .gitignore
├── .deepsource.toml
├── heroku.yml
├── example.env
├── routes
├── index.js
├── user.route.js
├── problem.route.js
└── submission.route.js
├── Service
├── jwt.js
└── auth.js
├── docker-compose.yaml
├── api_response.js
├── Dockerfile
├── package.json
├── models
├── User.js
├── Submission.js
├── Problem.js
└── index.js
├── README.md
└── index.js
/.dockerignore:
--------------------------------------------------------------------------------
1 | /node_modules
--------------------------------------------------------------------------------
/config/.gitignore:
--------------------------------------------------------------------------------
1 | config.json
2 |
--------------------------------------------------------------------------------
/Frontend/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | .env
3 |
--------------------------------------------------------------------------------
/controller/judge/TEST/.gitignore:
--------------------------------------------------------------------------------
1 | *.cpp
2 | *.out
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /db
3 | rest.http
4 | .env
5 |
--------------------------------------------------------------------------------
/Frontend/src/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rahathossain690/GlaxOJ/HEAD/Frontend/src/logo.png
--------------------------------------------------------------------------------
/Frontend/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/Frontend/src/carbon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rahathossain690/GlaxOJ/HEAD/Frontend/src/carbon.png
--------------------------------------------------------------------------------
/Frontend/src/g_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rahathossain690/GlaxOJ/HEAD/Frontend/src/g_icon.png
--------------------------------------------------------------------------------
/Frontend/src/glaxoj.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rahathossain690/GlaxOJ/HEAD/Frontend/src/glaxoj.png
--------------------------------------------------------------------------------
/Frontend/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rahathossain690/GlaxOJ/HEAD/Frontend/public/favicon.ico
--------------------------------------------------------------------------------
/Frontend/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rahathossain690/GlaxOJ/HEAD/Frontend/public/logo192.png
--------------------------------------------------------------------------------
/Frontend/src/util/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rahathossain690/GlaxOJ/HEAD/Frontend/src/util/logo.png
--------------------------------------------------------------------------------
/Frontend/src/util/UserContext.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export const UserContext = React.createContext()
--------------------------------------------------------------------------------
/.deepsource.toml:
--------------------------------------------------------------------------------
1 | version = 1
2 |
3 | [[analyzers]]
4 | name = "javascript"
5 | enabled = true
6 |
7 | [analyzers.meta]
8 | plugins = ["react"]
--------------------------------------------------------------------------------
/heroku.yml:
--------------------------------------------------------------------------------
1 | setup:
2 | addons:
3 | - plan: heroku-postgresql
4 | build:
5 | docker:
6 | web: ./Dockerfile
7 | config:
8 | REQUIREMENTS_FILENAME: heroku.yml
9 |
--------------------------------------------------------------------------------
/example.env:
--------------------------------------------------------------------------------
1 | PORT=
2 | PAGINATION=
3 | GOOGLE_CLIENT_ID=
4 | GOOGLE_CLIENT_SECRET=
5 | SESSION_SECRET=
6 | JUDGE_QUEUE_CAPACITY=
7 | JUDGE_QUEUE_CONCURRENCY=
8 | ADMIN_NAME=
9 | ADMIN_EMAIL=
10 | ADMIN_PICTURE=
--------------------------------------------------------------------------------
/Frontend/postcss.config.js:
--------------------------------------------------------------------------------
1 | const cssnano = require("cssnano");
2 | module.exports = {
3 | plugins: [
4 | require("tailwindcss"),
5 | require("autoprefixer"),
6 | cssnano({
7 | preset: "default",
8 | })
9 | ],
10 | };
11 |
--------------------------------------------------------------------------------
/Frontend/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect';
6 |
--------------------------------------------------------------------------------
/Frontend/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import App from './App';
4 |
5 | test('renders learn react link', () => {
6 | const { getByText } = render( );
7 | const linkElement = getByText(/learn react/i);
8 | expect(linkElement).toBeInTheDocument();
9 | });
10 |
--------------------------------------------------------------------------------
/Frontend/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "GlaxOJ",
3 | "name": "Your friendly online judge",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/routes/index.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const router = require('express').Router()
3 |
4 | // its magic
5 |
6 | fs.readdirSync(__dirname).filter(file => {
7 | return file.length > 9 && file.slice(-9 === '.route.js')
8 | }).forEach(file => {
9 | const route = file.split('.')[0]
10 | const route_function = require('./' + file)
11 | router.use('/' + route, route_function)
12 | })
13 |
14 | module.exports = router
--------------------------------------------------------------------------------
/Frontend/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | darkMode: 'media',
3 | important: true,
4 | //Purging for Production is configured in PostCSS Config
5 | purge:{
6 | content: ["./src/**/*.html", "./src/**/*.jsx", "./src/**/*.js"],
7 | },
8 | theme: {
9 | extend: {
10 |
11 | },
12 | },
13 | variants: {
14 | extend: {
15 | backgroundColor: ['active'],
16 | }
17 | },
18 | plugins: [],
19 | };
20 |
--------------------------------------------------------------------------------
/Service/jwt.js:
--------------------------------------------------------------------------------
1 | const jwt = require('jsonwebtoken')
2 |
3 |
4 | module.exports = {
5 | sign: (to_sign) => {
6 | return jwt.sign({
7 | secret: to_sign
8 | }, process.env.SESSION_SECRET, { expiresIn: 60 * 60 * 24 * 7 }); // 7 days
9 | },
10 |
11 | verify: (token) => {
12 | try {
13 | var decoded = jwt.verify(token, process.env.SESSION_SECRET);
14 | return decoded.secret;
15 | } catch(err) {
16 | return null;
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | version: "3"
2 | services:
3 | app:
4 | build:
5 | dockerfile: Dockerfile
6 | depends_on:
7 | - "db"
8 | restart: unless-stopped
9 | working_dir: /var/www
10 | ports:
11 | - '3000:3000'
12 | environment:
13 | CONNECTION_STRING: "postgresql://${DB_USER}:${DB_PASSWORD}@db:${DB_PORT}/${DB_DATABASE}"
14 |
15 |
16 | #db
17 | db:
18 | image: postgres
19 | restart: unless-stopped
20 | ports:
21 | - '5432:5432'
22 | env_file:
23 | - database.env
--------------------------------------------------------------------------------
/Frontend/src/util/config.js:
--------------------------------------------------------------------------------
1 |
2 | const data = {
3 | BACKEND_URL : 'https://glaxoj-backend.herokuapp.com',
4 | FRONTEND_URL: 'https://glaxoj.herokuapp.com',
5 | PAGINATION: 50,
6 | BACKEND_AUTHENTICATION_URL: 'https://glaxoj-backend.herokuapp.com/auth/google'
7 | }
8 |
9 | const dataLocal = {
10 | BACKEND_URL : 'http://localhost:3000',
11 | FRONTEND_URL: 'http://localhost:3001',
12 | PAGINATION: 50,
13 | BACKEND_AUTHENTICATION_URL: 'http://localhost:3000/auth/google'
14 | }
15 |
16 | module.exports = (process.env.NODE_ENV === 'development' ? dataLocal : data);
--------------------------------------------------------------------------------
/Frontend/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import * as serviceWorker from './serviceWorker';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | );
13 |
14 | // If you want your app to work offline and load faster, you can change
15 | // unregister() to register() below. Note this comes with some pitfalls.
16 | // Learn more about service workers: https://bit.ly/CRA-PWA
17 | serviceWorker.unregister();
18 |
--------------------------------------------------------------------------------
/Frontend/src/component/Footer.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import { Link } from 'react-router-dom'
4 |
5 | function Footer() {
6 | return (
7 |
12 | );
13 | }
14 |
15 | export default Footer;
16 |
--------------------------------------------------------------------------------
/api_response.js:
--------------------------------------------------------------------------------
1 | module.exports.SUCCESS = (data, message) => {
2 | return {
3 | success: "yes",
4 | data: data,
5 | message: message
6 | }
7 | }
8 |
9 |
10 | module.exports.FAILURE = (message, data) => {
11 | return {
12 | success: "no",
13 | message: message,
14 | data: data
15 | }
16 | }
17 |
18 | module.exports.STATUS = {
19 | OK: 200,
20 | CREATED: 201,
21 | NO_CONTENT: 204,
22 | BAD_REQUEST: 400,
23 | UNAUTHORIZED: 401,
24 | FORBIDDEN: 403,
25 | NOT_FOUND: 404,
26 | CONFLICT: 409,
27 | SERVER_ERROR: 500
28 | }
--------------------------------------------------------------------------------
/config/config.example.json:
--------------------------------------------------------------------------------
1 | {
2 | "development": {
3 | "username": "postgres",
4 | "password": "glaxoj",
5 | "database": "glaxoj",
6 | "host": "localhost",
7 | "dialect": "postgres",
8 | "logging": false
9 | },
10 | "production": {
11 | "username": "YOUR USERNAME",
12 | "password": "YOUR PASSWORD",
13 | "database": "YOUR DATABASE",
14 | "host": "YOUR HOST",
15 | "port": "YOUR PORT",
16 | "dialect": "postgres",
17 | "logging": false,
18 | "ssl": {
19 | "rejectUnauthorized": false
20 | },
21 | "dialectOptions": {
22 | "ssl": true
23 | },
24 | "url": "YOUR URL"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Frontend/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 | GlaxOJ
15 |
16 |
17 | You need to enable JavaScript to run this app.
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/Frontend/src/tailwind.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 |
3 | @tailwind components;
4 |
5 | .react-date-picker__wrapper {
6 | border: none !important;
7 | }
8 |
9 | .lds-dual-ring {
10 | display: inline-block;
11 | width: 80px;
12 | height: 80px;
13 | }
14 |
15 | .lds-dual-ring:after {
16 | content: " ";
17 | display: block;
18 | width: 64px;
19 | height: 64px;
20 | margin: 8px;
21 | border-radius: 50%;
22 | border: 6px solid #ccc;
23 | border-color: #ccc transparent #ccc transparent;
24 | animation: lds-dual-ring 1.2s linear infinite;
25 | }
26 |
27 | @keyframes lds-dual-ring {
28 | 0% {
29 | transform: rotate(0deg);
30 | }
31 | 100% {
32 | transform: rotate(360deg);
33 | }
34 | }
35 |
36 | @tailwind utilities;
37 |
--------------------------------------------------------------------------------
/routes/user.route.js:
--------------------------------------------------------------------------------
1 | const router = require('express').Router()
2 |
3 | const user_controller = require('../controller/user/user.controller')
4 |
5 | const auth_service = require('../Service/auth')
6 |
7 |
8 | router.get('/', user_controller.get_user)
9 | router.put('/', auth_service.is_admin, user_controller.update_user),
10 | router.get('/all', auth_service.is_admin, user_controller.get_all_user)
11 | router.get('/count', auth_service.is_admin, user_controller.count_user)
12 |
13 | router.get('/is_user', auth_service.is_user, user_controller.nothing)
14 | router.get('/is_admin', auth_service.is_admin, user_controller.nothing)
15 |
16 |
17 | router.get('/logout', user_controller.logout)
18 | // router.get('/set', user_controller.set_session_temporary)
19 |
20 | module.exports = router
21 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:14
2 |
3 | # Create app directory
4 | WORKDIR /usr/src/app
5 |
6 | # Install app dependencies
7 | # A wildcard is used to ensure both package.json AND package-lock.json are copied
8 | # where available (npm@5+)
9 | COPY package*.json ./
10 |
11 | RUN apt-get update && apt-get install build-essential -y
12 |
13 | #RUN apt-get install -y postgresql-client-9.5
14 |
15 | #RUN apt-get -y install postgresql
16 |
17 |
18 | # ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.7.3/wait /wait
19 | # RUN chmod +x /wait
20 |
21 | RUN npm install
22 | # If you are building your code for production
23 | # RUN npm ci --only=production
24 |
25 | # Bundle app source
26 | COPY . .
27 |
28 | # EXPOSE 3000
29 | # EXPOSE 5432
30 |
31 | ENV NODE_ENV production
32 |
33 |
34 | CMD [ "node", "index.js" ]
--------------------------------------------------------------------------------
/routes/problem.route.js:
--------------------------------------------------------------------------------
1 | const router = require('express').Router()
2 |
3 | const problem_controller = require('../controller/problem/problem.controller')
4 | const problem_validation = require('../controller/problem/problem.validation')
5 |
6 | const auth_service = require('../Service/auth')
7 |
8 | router
9 | .post('/', auth_service.is_admin, problem_validation.create_problem, problem_controller.create_problem)
10 | .get('/', auth_service.parse, problem_validation.get_problem, problem_controller.get_problem)
11 | .put('/', auth_service.is_admin, problem_validation.problem_id_query, problem_validation.update_problem, problem_controller.update_problem)
12 | .delete('/', auth_service.is_admin, problem_validation.problem_id_query, problem_controller.delete_problem)
13 | .get('/search', auth_service.parse, problem_validation.search_problem, problem_controller.search_problem)
14 |
15 | module.exports = router;
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "glaxoj-backend",
3 | "version": "1.0.0",
4 | "description": "Backend for Glaxoj site",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "node index.js",
9 | "dev": "nodemon"
10 | },
11 | "author": "Rahat Hossain",
12 | "license": "ISC",
13 | "dependencies": {
14 | "connect-pg-simple": "^6.2.1",
15 | "cookie-parser": "^1.4.5",
16 | "cors": "^2.8.5",
17 | "dotenv": "^8.2.0",
18 | "express": "^4.17.1",
19 | "express-rate-limit": "^5.2.6",
20 | "joi": "^17.4.0",
21 | "jsonwebtoken": "^8.5.1",
22 | "passport": "^0.4.1",
23 | "passport-google-oauth20": "^2.0.0",
24 | "pg": "^8.5.1",
25 | "pg-hstore": "^2.3.3",
26 | "sequelize": "^6.5.0",
27 | "task-queue": "^1.0.2"
28 | },
29 | "devDependencies": {
30 | "nodemon": "^2.0.7",
31 | "sequelize-cli": "^6.2.0"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/controller/submission/submission.validation.js:
--------------------------------------------------------------------------------
1 | const joi = require('joi')
2 | const {FAILURE, STATUS} = require('../../api_response')
3 |
4 |
5 | const joi_check = (data, check_object_schema) => {
6 | const validation = joi.object(check_object_schema).validate(data)
7 | if(!!validation.error) return validation.error.details.map(item => item.message)
8 | return false
9 | }
10 |
11 |
12 | module.exports = {
13 |
14 | submit: (req, res, next) => {
15 | const validation_error = joi_check(req.body, {
16 |
17 |
18 | problem_id: joi.number().required(),
19 | source_code: joi.string().max(2000).required(),
20 | language: 'cpp'
21 |
22 | })
23 | if (!!validation_error) {
24 | res.status(STATUS.BAD_REQUEST).send( FAILURE(validation_error) )
25 | return validation_error;
26 |
27 | } else {
28 | next()
29 | }
30 | },
31 |
32 | }
--------------------------------------------------------------------------------
/models/User.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = (sequelize, DataTypes) => {
3 |
4 | const User = sequelize.define('User', {
5 | username: {
6 | type: DataTypes.STRING,
7 | primaryKey: true,
8 | allowNull: false
9 | },
10 | fullname: {
11 | type: DataTypes.STRING,
12 | allowNull: false
13 | },
14 | email: {
15 | type: DataTypes.STRING,
16 | allowNull: false,
17 | },
18 | image: {
19 | type: DataTypes.STRING
20 | },
21 | disable: {
22 | type: DataTypes.BOOLEAN,
23 | defaultValue: false
24 | },
25 | role: {
26 | type: DataTypes.ARRAY(DataTypes.STRING),
27 | default: []
28 | }
29 | })
30 |
31 | User.associate = (models => {
32 | User.hasMany(models.Submission, {
33 | foreignKey: 'username'
34 | })
35 | })
36 |
37 | return User;
38 | }
--------------------------------------------------------------------------------
/Frontend/src/page/NF.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 |
4 | function NF() {
5 |
6 | return (
7 |
8 |
9 |
10 | 404
11 |
12 |
13 |
Page is not available.
14 |
15 |
18 |
19 |
Feel free to share any suggestion, complaint, feedback, thoughts.
20 |
21 |
22 | );
23 | }
24 |
25 | export default NF;
26 |
--------------------------------------------------------------------------------
/models/Submission.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = (sequelize, DataTypes) => {
3 |
4 | const Submission = sequelize.define('Submission', {
5 | id: {
6 | type: DataTypes.INTEGER,
7 | primaryKey: true,
8 | autoIncrement: true
9 | },
10 | username: {
11 | type: DataTypes.STRING,
12 | allowNull: false
13 | },
14 | problem_id: {
15 | type: DataTypes.INTEGER,
16 | allowNull: false,
17 | },
18 | language: {
19 | type: DataTypes.STRING,
20 | allowNull: false
21 | },
22 | verdict: {
23 | type: DataTypes.STRING,
24 | defaultValue: "Not Judged Yet"
25 | },
26 | source_code: {
27 | type: DataTypes.STRING(1000000),
28 | allowNull: false
29 | }
30 | })
31 |
32 | Submission.associate = (models => {
33 | Submission.belongsTo(models.User, {
34 | foreignKey: 'username',
35 | onDelete: 'CASCADE'
36 | })
37 |
38 | Submission.belongsTo(models.Problem, {
39 | foreignKey: 'problem_id',
40 | onDelete: 'CASCADE'
41 | })
42 | })
43 |
44 | return Submission;
45 | }
46 |
--------------------------------------------------------------------------------
/routes/submission.route.js:
--------------------------------------------------------------------------------
1 | const router = require('express').Router()
2 |
3 | const submission_controller = require('../controller/submission/submission.controller')
4 | const submission_validation = require('../controller/submission/submission.validation')
5 |
6 |
7 | const auth_service = require('../Service/auth')
8 |
9 |
10 | const rateLimit = require("express-rate-limit");
11 | const { route } = require('.');
12 |
13 | const limiter = rateLimit({
14 | windowMs: 5 * 60 * 1000, // 5 minutes
15 | max: 5 // limit each IP to 5 requests per windowMs
16 | });
17 |
18 |
19 | router.post('/submit', limiter, auth_service.is_user, submission_validation.submit, submission_controller.submit)
20 | router.get('/', auth_service.is_user, submission_controller.get_submission)
21 | router.get('/status', submission_controller.status)
22 | router.delete('/', auth_service.is_admin, submission_controller.delete_submission)
23 |
24 | router.get('/rank', submission_controller.rank)
25 | router.get('/count', submission_controller.count)
26 | // router.get('/ac', submission_controller.get_ac)
27 |
28 | router.get('/judge_status', auth_service.is_admin, submission_controller.get_judge_status)
29 | router.post('/judge_toggle', auth_service.is_admin, submission_controller.toggle_judge)
30 |
31 |
32 | // router.get('/position', submission_controller.position) // this function is sad :(
33 |
34 | module.exports = router
--------------------------------------------------------------------------------
/Service/auth.js:
--------------------------------------------------------------------------------
1 | const {User} = require('../models')
2 | const {STATUS, FAILURE} = require('../api_response')
3 |
4 | const {verify} = require('./jwt')
5 |
6 |
7 | const authenticate = async(cookie) => {
8 | const username = verify(cookie);
9 | if(!username) return null;
10 | const user = await User.findOne({
11 | where: {
12 | username,
13 | },
14 | raw: true
15 | })
16 | if(user.role == null) user.role = []
17 | return user
18 | }
19 |
20 | module.exports = {
21 |
22 | is_admin: async(req, res, next) => {
23 | // if(process.env.ONLY_RAHAT_ISREAL) req.session.username = 'rahathossain' // TODO: DELETE THIS
24 | req.locals = await authenticate(req.cookies.glaxoj)
25 | if(req.locals && req.locals.role.indexOf('ADMIN') !== -1) next()
26 | else res.status(STATUS.UNAUTHORIZED).send( FAILURE("not an admin") )
27 | },
28 |
29 | is_user: async(req, res, next) => {
30 | // if(process.env.ONLY_RAHAT_ISREAL) req.session.username = 'rahathossain'// TODO: DELETE THIS
31 | req.locals = await authenticate(req.cookies.glaxoj)
32 | if(req.locals) next()
33 | else res.status(STATUS.UNAUTHORIZED).send( FAILURE("not an user") )
34 | },
35 |
36 | parse : async(req, res, next) => {
37 | // if(process.env.ONLY_RAHAT_ISREAL) req.session.username = 'rahathossain'// TODO: DELETE THIS
38 | req.locals = await authenticate(req.cookies.glaxoj)
39 | next()
40 | }
41 | }
--------------------------------------------------------------------------------
/models/Problem.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = (sequelize, DataTypes) => {
3 |
4 | const Problem = sequelize.define('Problem', {
5 | id: {
6 | type: DataTypes.INTEGER,
7 | primaryKey: true,
8 | autoIncrement: true
9 | },
10 | tag: {
11 | type: DataTypes.ARRAY(DataTypes.STRING),
12 | allowNull: false
13 | },
14 | title: {
15 | type: DataTypes.STRING,
16 | allowNull: false,
17 | unique: true
18 | },
19 | content: {
20 | type: DataTypes.STRING(100000),
21 | allowNull: false
22 | },
23 | inputs: {
24 | type: DataTypes.ARRAY(DataTypes.TEXT),
25 | allowNull: false
26 | },
27 | outputs: {
28 | type: DataTypes.ARRAY(DataTypes.TEXT),
29 | allowNull: false
30 | },
31 | author: {
32 | type: DataTypes.STRING,
33 | },
34 | time_limit: {
35 | type: DataTypes.DECIMAL,
36 | allowNull: false
37 | },
38 | memory_limit: {
39 | type: DataTypes.DECIMAL,
40 | allowNull: false
41 | },
42 | disable: {
43 | type: DataTypes.BOOLEAN,
44 | defaultValue: false
45 | },
46 | })
47 |
48 | Problem.associate = (models => {
49 | Problem.hasMany(models.Submission, {
50 | foreignKey: 'problem_id'
51 | })
52 | })
53 |
54 |
55 | return Problem;
56 | }
57 |
--------------------------------------------------------------------------------
/models/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 | const path = require('path');
5 | const Sequelize = require('sequelize');
6 | const basename = path.basename(__filename);
7 | const env = process.env.NODE_ENV || 'development';
8 | const config = require(__dirname + '/../config/config.json')[env];
9 | const db = {};
10 |
11 | let sequelize;
12 | // console.log(env, config)
13 |
14 | //config.host = 'pqsql'
15 |
16 | //console.log(process.env.CONNECTION_STRING)
17 |
18 | if(config.url) {
19 | sequelize = new Sequelize(config.url, {
20 | dialect: 'postgres',
21 | logging: false,
22 | dialectOptions: {
23 | ssl: {
24 | require: true,
25 | rejectUnauthorized: false
26 | },
27 | }
28 | }
29 | );
30 | } else if (config.use_env_variable) {
31 | sequelize = new Sequelize(process.env[config.use_env_variable], config);
32 | } else {
33 | sequelize = new Sequelize(config.database, config.username, config.password, config);
34 | }
35 |
36 |
37 | fs
38 | .readdirSync(__dirname)
39 | .filter(file => {
40 | return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
41 | })
42 | .forEach(file => {
43 | const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes);
44 | db[model.name] = model;
45 | });
46 |
47 | Object.keys(db).forEach(modelName => {
48 | if (db[modelName].associate) {
49 | db[modelName].associate(db);
50 | }
51 | });
52 |
53 | db.sequelize = sequelize;
54 | db.Sequelize = Sequelize;
55 |
56 | module.exports = db;
57 |
--------------------------------------------------------------------------------
/Frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-tailwind-starter",
3 | "version": "0.1.0",
4 | "private": true,
5 | "engines": {
6 | "npm": "6.14.6",
7 | "node": "12.18.4"
8 | },
9 | "dependencies": {
10 | "@testing-library/jest-dom": "^4.2.4",
11 | "@testing-library/react": "^9.5.0",
12 | "@testing-library/user-event": "^7.2.1",
13 | "query-string": "^6.14.1",
14 | "react": "^16.13.1",
15 | "react-ace": "^9.3.0",
16 | "react-dom": "^16.13.1",
17 | "react-loading": "^2.0.3",
18 | "react-router-dom": "^5.2.0",
19 | "react-rte": "^0.16.3",
20 | "react-scripts": "^3.4.3",
21 | "react-typical": "^0.1.3",
22 | "tailwindcss": "^1.4.6"
23 | },
24 | "scripts": {
25 | "purge:css": "cross-env NODE_ENV=production postcss src/tailwind.css -o src/index.css",
26 | "build:css": "postcss src/tailwind.css -o src/index.css",
27 | "watch:css": "postcss src/tailwind.css -o src/index.css -w",
28 | "start": "npm run build:css && react-scripts start",
29 | "build": "npm run purge:css && react-scripts build",
30 | "test": "react-scripts test",
31 | "eject": "react-scripts eject"
32 | },
33 | "eslintConfig": {
34 | "extends": "react-app"
35 | },
36 | "browserslist": {
37 | "production": [
38 | ">0.2%",
39 | "not dead",
40 | "not op_mini all"
41 | ],
42 | "development": [
43 | "last 1 chrome version",
44 | "last 1 firefox version",
45 | "last 1 safari version"
46 | ]
47 | },
48 | "devDependencies": {
49 | "autoprefixer": "^9.8.0",
50 | "cross-env": "^7.0.2",
51 | "cssnano": "^4.1.10",
52 | "postcss-cli": "^7.1.1"
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Frontend/src/page/Main.js:
--------------------------------------------------------------------------------
1 | import React, {useEffect, useState} from 'react';
2 | import Typical from 'react-typical'
3 | import Footer from '../component/Footer';
4 |
5 | import logo from '../logo.png'
6 | import quotes from '../util/quotes.json'
7 |
8 | function Main() {
9 |
10 | const [quote, setQuote] = useState({quote: "Life is good", by: "Rahat Hossain"})
11 |
12 | const TypingAnimation = React.memo(()=>{
13 | return
18 | },(props,prevProp)=> true );
19 |
20 | useEffect(() => {
21 | setQuote(quotes[ Math.floor( Math.random() * quotes.length ) + 1 ]);
22 | //console.log(quote)
23 | }, [])
24 |
25 | return (
26 |
27 |
28 |
29 |
30 | Your friendly online judge
31 |
32 |
37 |
38 |
39 | {quote?.quote || "Work work work work work."}
40 |
41 |
42 | - {quote?.by || "Rihanna"}
43 |
44 |
45 |
46 |
47 | );
48 | }
49 |
50 | export default Main;
51 |
--------------------------------------------------------------------------------
/controller/judge/index.js:
--------------------------------------------------------------------------------
1 | const Runner = require('./Runner')
2 | const tq = require('task-queue');
3 | const path = require('path')
4 |
5 | const fs = require('fs')
6 | const PATH = path.join(__dirname, 'TEST')
7 |
8 | try {
9 | if (fs.existsSync(PATH.toString())) {
10 | //file exists
11 | } else {
12 | fs.mkdirSync(PATH.toString());
13 | }
14 | } catch(err) {
15 | console.error(err)
16 | }
17 |
18 | const queue = tq.Queue({
19 | capacity: parseInt(process.env.JUDGE_QUEUE_CAPACITY) ,
20 | concurrency: parseInt(process.env.JUDGE_QUEUE_CONCURRENCY)
21 | });
22 |
23 | module.exports = {
24 | start_judge_queue: () => {
25 | queue.start()
26 | },
27 | stop_judge_queue: () => {
28 | queue.stop()
29 | },
30 | // main judge driver
31 | add_to_queue: async ({submission_id, source_code, language, inputs, outputs, time_limit}, callback) => {
32 |
33 | queue.enqueue(async () => {
34 |
35 | // console.log(submission_id, source_code, language, inputs, outputs, time_limit)
36 | let verdict = 'Accepted';
37 | for(let i = 0; i < inputs.length; i++) {
38 | try{
39 | const result = await new Runner(source_code, language, inputs[i], time_limit * 1000, null, PATH).run()
40 | if(result.output !== outputs[i]){
41 | verdict = 'Wrong Answer'
42 | break;
43 | }
44 | } catch(err){ //console.log(err)
45 | const error_msg = err.error
46 | if(error_msg !== "Time Limit Exceeded" && error_msg !== "Compilation Error" && error_msg !== "Memory Limit Exceeded" && error_msg !== "Runtime Error") {
47 | error_msg = "Unknown Error"
48 | }
49 | verdict = error_msg
50 | break;
51 | }
52 | }
53 | // console.log(verdict)
54 | callback(verdict)
55 | })
56 |
57 | },
58 | judge_state: () => {
59 | return queue.isRunning()
60 | },
61 | size: () => {
62 | return queue.size()
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Frontend/src/page/Auth.js:
--------------------------------------------------------------------------------
1 |
2 | import React, { useContext, useEffect } from 'react';
3 | import { useHistory, useLocation } from 'react-router-dom';
4 |
5 | import queryString from 'query-string'
6 | import ReactLoading from 'react-loading';
7 |
8 | import {BACKEND_URL} from '../util/config'
9 | import {UserContext} from '../util/UserContext'
10 |
11 |
12 | function NF() {
13 | const query = queryString.parse(useLocation().search)
14 | const history = useHistory()
15 |
16 | //console.log(query)
17 | const [userFromContext, setUserFromContext] = useContext(UserContext)
18 |
19 | useEffect(()=>{
20 |
21 | try{
22 |
23 | (async()=>{
24 |
25 | const response = await fetch( `${BACKEND_URL}/auth`, {
26 | credentials: 'include',
27 | method: 'POST',
28 | headers: {
29 | 'Content-Type': 'application/json'
30 | },
31 | body: JSON.stringify(query)
32 | })
33 | const data = await response.json()
34 | if(data.success === "no") {
35 | // fuck off
36 | } else {
37 | // get user data
38 | const response2 = await fetch( `${BACKEND_URL}/user`, {
39 | credentials: 'include',
40 | withCredentials: true,
41 | headers: {
42 | 'Content-Type': 'application/json'
43 | },
44 | })
45 | const data2 = await response2.json()
46 |
47 | // console.log(data2)
48 | if(data2.success === "yes") {
49 | setUserFromContext(data2.data)
50 | }
51 | history.push('/')
52 |
53 |
54 | }
55 |
56 |
57 | })()
58 |
59 | } catch(err){
60 |
61 | }
62 |
63 | }, [])
64 |
65 | return (
66 |
71 | );
72 | }
73 |
74 | export default NF;
75 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GlaxOJ
2 | GlaxOJ is an online judge application. Like all online judges, it contains a set of coding problems, allows its authenticated users to solve them and get verdicts. It allows some extended features too that users can enjoy.
3 |
4 | Live site: [GlaxOJ](https://glaxoj-frontend.onrender.com/)
5 |
6 | 
7 |
8 | # Tech-Stack
9 | ##### Frontend
10 | URL: [./Frontend](https://github.com/rahathossain690/GlaxOJ/tree/master/Frontend)
11 | 1. Tailwindcss
12 | 2. React
13 |
14 |
15 | ##### Backend
16 | 1. Node-js
17 | 2. PostgreSQL database
18 | 3. Sequelize ORM
19 |
20 | and Dockerized.
21 |
22 | ##### Deployment
23 | 1. Render with docker
24 |
25 | # Installation
26 | 1. Install nodejs.
27 | 2. Install npm.
28 | 3. Install postgreSQL and configure database and provide your configuration here [./config](https://github.com/rahathossain690/GlaxOJ/tree/master/config).
29 | 4. Type `npm install` and `cd Frontend && npm install`.
30 | 5. Rewrite your preferable .env files.
31 | 6. Type `npm start` for backend and `cd Frontend && npm install` for frontend.
32 |
33 | # Features / Usage
34 | Users can -
35 | ##### Problem
36 | 1. View problems with Pagination.
37 | 2. Query over problem properties.
38 | 3. view a specific problem.
39 | 4. Submit solution (Currently supports C++ only).
40 |
41 |
42 | ##### Rank
43 | 1. View the ranklist.
44 |
45 |
46 | ##### Status
47 | 1. View current judge status.
48 | 2. Query over submission properties.
49 |
50 |
51 | ##### User
52 | 1. View user informations like total submissions, solves, solved problems, tried problems etc.
53 | 2. View user's submissions.
54 |
55 |
56 | ##### Authentication
57 | 1. Can authenticate with Google OAuth only.
58 |
59 |
60 | ##### Admin (Restricted)
61 | 1. Create / disable / update / delete problems.
62 | 2. Watch out the users (Along with the power of disabling another user, giving / taking adminstration power to other), submissions and other informations.
63 | 3. Remove submission.
64 | 4. Turn judge status on / off.
65 |
66 | # Contribution
67 | Feel free to contribute and share your thoughts on this project.
68 |
69 | # Acknowledgment
70 | (As of april, 2021) This is OJ is based on a long time dream and a so-called short time class project (Thanks to COVID). The name was suggested by [Farhan Fuad](https://github.com/farhanfuad35) bro. A big shoutout and a little thanks to [Faiaz Khan](https://github.com/faiazamin) bro. A little thanks to [NJ Rafi](https://github.com/njrafi) vai too. Rest of all the big thanks go to stack-overflow, all the stack-docs, all the kind hearted wingless angels and their tech blogs, github issues and all the gods solving there and my lost hairs / sleeps / will to live / chance of getting a girlfriend.
71 |
--------------------------------------------------------------------------------
/Frontend/src/util/quotes.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "quote": "A problem is a chance for you to do your best.",
4 | "by": "Duke Ellington"
5 | },
6 | {
7 | "quote": "If your only tool is a hammer then every problem looks like a nail.",
8 | "by": "Abraham Maslow "
9 | },
10 | {
11 | "quote": "If your only tool is a hammer then every problem looks like a nail.",
12 | "by": "Abraham Maslow "
13 | },
14 | {
15 | "quote": "If I had an hour to solve a problem I'd spend 55 minutes thinking about the problem and 5 minutes thinking about solutions.",
16 | "by": "Albert Einstein"
17 | },
18 | {
19 | "quote": "There are not more than five musical notes, yet the combinations of these five give rise to more melodies than can ever be heard. There are not more than five primary colours, yet in combination they produce more hues than can ever been seen. There are not more than five cardinal tastes, yet combinations of them yield more flavours than can ever be tasted.",
20 | "by": "Sun Tzu"
21 | },
22 | {
23 | "quote": "Well, if it can be thought, it can be done, a problem can be overcome.",
24 | "by": "E.A. Bucchianeri"
25 | },
26 | {
27 | "quote": "It is better to solve one problem five different ways, than to solve five problems one way.",
28 | "by": "George Pólya"
29 | },
30 | {
31 | "quote": "Leadership is self-made. People who have deliberately decided to become problems solver lead better.",
32 | "by": "Israelmore Ayivor"
33 | },
34 | {
35 | "quote": "Problems are not stop signs, they are guidelines.",
36 | "by": "Robert H. Shuller"
37 | },
38 | {
39 | "quote": "All problems become smaller when you confront them instead of dodging them.",
40 | "by": "William F. Halsey"
41 | },
42 | {
43 | "quote": "Every problem is a gift. Without them we wouldn’t grow.",
44 | "by": "Tony Robbins"
45 | },
46 | {
47 | "quote": "It isn’t that they cannot find the solution. It is that they cannot see the problem.",
48 | "by": "G.K Chesterton"
49 | },
50 | {
51 | "quote": "Problems are nothing but wake-up calls for creativity.",
52 | "by": "Gerhard Gschwandtner"
53 | },
54 | {
55 | "quote": "Each problem that I solved became a rule, which served afterwards to solve other problems.",
56 | "by": "Rene Descartes"
57 | },
58 | {
59 | "quote": "You can increase your problem-solving skills by honing your question-asking ability.",
60 | "by": "Michael J. Gelb"
61 | },
62 | {
63 | "quote": "All life is problem solving.",
64 | "by": "Karl Popper"
65 | }
66 | ]
--------------------------------------------------------------------------------
/Frontend/src/component/Nav.js:
--------------------------------------------------------------------------------
1 |
2 | import React, { useContext, useEffect } from 'react';
3 | import { Link, useHistory } from 'react-router-dom'
4 |
5 | import {BACKEND_AUTHENTICATION_URL} from '../util/config'
6 | import {UserContext} from '../util/UserContext'
7 |
8 | import glaxoj from '../glaxoj.png'
9 |
10 | function Nav() {
11 |
12 | const history = useHistory()
13 |
14 | const [user, setUser] = useContext(UserContext)
15 |
16 | // console.log('user from context', user)
17 |
18 | const goto = async(username) => {
19 | history.push(`/user/${username}`)
20 | // history.go(0)
21 | }
22 |
23 | return (
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
Problem
33 |
Rank
34 |
Status
35 | {!user &&
36 |
Join
37 | }
38 | {user &&
39 |
goto(user.username)}>{user.username}
40 | }
41 | { user && user.role && user.role.indexOf('ADMIN') !== -1 &&
42 |
Admin
43 | }
44 |
45 |
46 |
47 | )
48 | }
49 |
50 | export default Nav;
51 |
--------------------------------------------------------------------------------
/Frontend/README.md:
--------------------------------------------------------------------------------
1 | This project was bootstrapped with [Create React Tailwind App](https://github.com/gigincg/create-react-tailwind-app).
2 |
3 | ## Available Scripts
4 |
5 | In the project directory, you can run:
6 |
7 | ### `npm start`
8 |
9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
11 |
12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console.
14 |
15 | ### `npm test`
16 |
17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
19 |
20 | ### `npm run build`
21 |
22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance.
24 |
25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed!
27 |
28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
29 |
30 | ### `npm run eject`
31 |
32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
33 |
34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
35 |
36 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
37 |
38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
39 |
40 | ## Learn More
41 |
42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
43 |
44 | To learn React, check out the [React documentation](https://reactjs.org/).
45 |
46 | ### Code Splitting
47 |
48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
49 |
50 | ### Analyzing the Bundle Size
51 |
52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
53 |
54 | ### Making a Progressive Web App
55 |
56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
57 |
58 | ### Advanced Configuration
59 |
60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
61 |
62 | ### Deployment
63 |
64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
65 |
66 | ### `npm run build` fails to minify
67 |
68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify
69 |
--------------------------------------------------------------------------------
/Frontend/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import { Route, BrowserRouter as Router, Switch} from 'react-router-dom'
3 |
4 | import Nav from './component/Nav'
5 |
6 | import Main from './page/Main'
7 | import NF from './page/NF'
8 | import Admin from './page/Admin'
9 | import AdminUser from './page/AdminUser'
10 | import User from './page/User'
11 | import Status from './page/Status';
12 | import Rank from './page/rank';
13 | import Problem from './page/Problem';
14 | import Submit from './page/Submit';
15 | import EditProblem from './page/EditProblem';
16 | import ShowProblem from './page/ShowProblem';
17 | import Submission from './page/Submission';
18 | import Auth from './page/Auth';
19 |
20 |
21 | import {BACKEND_URL} from './util/config'
22 | import {UserContext} from './util/UserContext'
23 |
24 | import ReactLoading from 'react-loading';
25 | // import queryString from 'query-string'
26 |
27 | function App() {
28 | // if(query.glaxoj) {
29 | // // localStorage.setItem('glaxoj', query.glaxoj)
30 | // history.push('/')
31 | // }
32 |
33 | const [user, setUser] = useState(null)
34 | const [loading, setLoading] = useState(true)
35 |
36 | useEffect(()=>{
37 |
38 | (async()=>{
39 | //console.log(`${BACKEND_URL}/user`)
40 | try{
41 | if(!user) {
42 | const response = await fetch( `${BACKEND_URL}/user`, {
43 | credentials: 'include',
44 | withCredentials: true,
45 | headers: {
46 | 'Content-Type': 'application/json'
47 | },
48 | })
49 | const data = await response.json()
50 | //console.log(data)
51 | if(data.success === "yes") {
52 | setUser(data.data)
53 | //console.log(user)
54 | } else {
55 | setUser(null)
56 | }
57 | }
58 |
59 | setLoading(false)
60 | } catch(err) {
61 | setLoading(false)
62 | }
63 | })()
64 |
65 | // console.log(user)
66 |
67 | },[])
68 |
69 |
70 |
71 | return (
72 |
73 | { loading &&
74 |
75 |
76 | }
77 | { !loading &&
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
}
100 |
101 | );
102 | }
103 |
104 | export default App;
105 |
--------------------------------------------------------------------------------
/controller/problem/problem.validation.js:
--------------------------------------------------------------------------------
1 | const joi = require('joi')
2 | const {FAILURE, STATUS} = require('../../api_response')
3 |
4 |
5 | const joi_check = (data, check_object_schema) => {
6 | const validation = joi.object(check_object_schema).validate(data)
7 | if(!!validation.error) return validation.error.details.map(item => item.message)
8 | return false
9 | }
10 |
11 |
12 | module.exports = {
13 |
14 | create_problem: (req, res, next) => {
15 | const validation_error = joi_check(req.body, {
16 |
17 | title: joi.string().required(),
18 | tag: joi.array().items(joi.string()).required(),
19 | content: joi.string().required(),
20 | inputs: joi.array().items(joi.string()).required(),
21 | outputs: joi.array().items(joi.string()).required(),
22 | author: joi.string(),
23 | time_limit: joi.number().integer().positive().required(),
24 | memory_limit: joi.number().integer().positive().required(),
25 | disable: joi.boolean()
26 |
27 | })
28 | if (!!validation_error) {
29 | res.status(STATUS.BAD_REQUEST).send( FAILURE(validation_error) )
30 | return validation_error;
31 |
32 | } else {
33 | next()
34 | }
35 | },
36 |
37 |
38 | problem_id_query: (req, res, next) => {
39 | const validation_error = joi_check(req.query, {
40 |
41 | id: joi.number().integer().positive().required()
42 |
43 | })
44 | if (!!validation_error) {
45 | res.status(STATUS.BAD_REQUEST).send( FAILURE(validation_error) )
46 | return validation_error;
47 |
48 | } else {
49 | next()
50 | }
51 | },
52 |
53 | update_problem: (req, res, next) => {
54 | const validation_error = joi_check(req.body, {
55 |
56 |
57 | title: joi.string(),
58 | tag: joi.array().items(joi.string()),
59 | content: joi.string(),
60 | inputs: joi.array().items(joi.string()),
61 | outputs: joi.array().items(joi.string()),
62 | author: joi.string(),
63 | time_limit: joi.number().integer().positive(),
64 | memory_limit: joi.number().integer().positive(),
65 | disable: joi.boolean()
66 |
67 |
68 | })
69 | if (!!validation_error) {
70 | res.status(STATUS.BAD_REQUEST).send( FAILURE(validation_error) )
71 | return validation_error;
72 |
73 | } else {
74 | next()
75 | }
76 | },
77 |
78 | get_problem: (req, res, next) => {
79 | const validation_error = joi_check(req.query, {
80 |
81 | id: joi.number().integer().positive().required(),
82 | admin: joi.boolean()
83 |
84 | })
85 | if (!!validation_error) {
86 | res.status(STATUS.BAD_REQUEST).send( FAILURE(validation_error) )
87 | return validation_error;
88 |
89 | } else {
90 | next()
91 | }
92 | },
93 |
94 | search_problem: (req, res, next) => {
95 | const validation_error = joi_check(req.query, {
96 |
97 | tag: joi.string(),
98 | author: joi.string(),
99 | title: joi.string(),
100 | admin: joi.boolean(),
101 | page: joi.number().integer().positive(),
102 |
103 | })
104 | if (!!validation_error) {
105 | res.status(STATUS.BAD_REQUEST).send( FAILURE(validation_error) )
106 | return validation_error;
107 |
108 | } else {
109 | next()
110 | }
111 | }
112 |
113 | }
--------------------------------------------------------------------------------
/Frontend/src/page/rank.js:
--------------------------------------------------------------------------------
1 |
2 | import React, { useEffect, useState } from 'react';
3 | import { Link, useHistory, useLocation } from 'react-router-dom';
4 | import queryString from 'query-string'
5 | import ReactLoading from 'react-loading';
6 |
7 | import {BACKEND_URL, PAGINATION} from '../util/config'
8 |
9 | function Rank() {
10 |
11 | const history = useHistory()
12 | const query = queryString.parse(useLocation().search)
13 | const [page, setPage] = useState( Math.max(1, parseInt(query.page) || 1 ) )
14 | const [rank, setRank] = useState(null)
15 |
16 | useEffect(() => {
17 |
18 | try{
19 |
20 | ( async() => {
21 |
22 | const response = await fetch( `${BACKEND_URL}/submission/rank?page=${page}`, {
23 | credentials: 'include',
24 | headers: {
25 | 'Content-Type': 'application/json'
26 | },
27 | })
28 | const data = await response.json()
29 | //console.log(data)
30 | if(data.success === "yes") setRank(data.data)
31 | else {
32 | history.push('/404')
33 | history.go(0)
34 | }
35 |
36 | } )()
37 |
38 | } catch(err) {
39 | history.push('/404')
40 | history.go(0)
41 | }
42 |
43 |
44 | }, [])
45 |
46 | const goto = () => {
47 | history.push(`/rank?page=${page}`)
48 | history.go(0)
49 | }
50 |
51 | const handleEnter = (event) => {
52 | if(event.key === 'Enter') goto()
53 | }
54 |
55 | return (
56 |
57 | {!rank &&
58 |
59 |
60 |
61 | }
62 | {rank &&
63 |
66 |
67 |
68 | Page
69 | setPage( Math.max( parseInt(e.target.value.trim()), 1 ) )} onKeyPress={handleEnter}/>
70 | ↣
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | Rank
79 | Username
80 | Solve
81 |
82 |
83 |
84 | { rank.map((individual, index) => {
85 | return (
86 | {index + 1 + ((query.page || 1) - 1) * PAGINATION}
87 | {individual.username}
88 | {individual.solve}
89 | )
90 | }) }
91 |
92 |
93 |
94 |
}
95 |
96 | );
97 | }
98 |
99 | export default Rank;
100 |
--------------------------------------------------------------------------------
/Frontend/src/page/Submit.js:
--------------------------------------------------------------------------------
1 |
2 | import React, { useState } from 'react';
3 | import { useHistory } from 'react-router';
4 |
5 | import {BACKEND_URL} from '../util/config'
6 | import AceEditor from "react-ace";
7 |
8 | function Submit(props) {
9 | const [problemId, setProblemId] = useState( props.match.params.problem_id )
10 | const [language, setLanguage] = useState( props.match.params.language )
11 | const [code, setCode] = useState("")
12 | const [buttonDisable, setButtonDisable] = useState(false)
13 | const [error, setError] = useState("")
14 | const history = useHistory()
15 |
16 | const submit = async () => {
17 | if(!language) {
18 | setError('Language is required')
19 | return;
20 | }
21 | if(buttonDisable) {
22 | setError('Slow down');
23 | return;
24 | }
25 | setButtonDisable(true);
26 | try{
27 | const post_data = {
28 | source_code: code,
29 | language,
30 | problem_id: problemId
31 | }
32 | // //console.log(post_data)
33 | const response = await fetch( `${BACKEND_URL}/submission/submit`, {
34 | credentials: 'include',
35 | method: 'POST',
36 | headers: {
37 | 'Content-Type': 'application/json'
38 | },
39 | body: JSON.stringify(post_data)
40 | })
41 | const response_data = await response.json()
42 | if(response_data.success === "yes") {
43 | history.push(`/submission/${response_data.data.id}`)
44 | setError("")
45 | } else{
46 | setButtonDisable(false)
47 | setError(response_data.message)
48 | }
49 | }catch(err){
50 | //console.log(err)
51 | setError('Error encountered')
52 | }
53 | }
54 |
55 |
56 | return (
57 |
58 |
59 | Submit
60 |
61 |
62 |
63 |
64 | setProblemId(e.target.value)} placeholder="Problem ID"/>
65 | setLanguage(e.target.value)} value={language} className="border border-blue-600 text-center m-2 p-1">
66 | Language
67 | cpp
68 |
69 |
70 |
71 |
100 | {error &&
101 |
submit()}>{error}
102 |
}
103 |
104 | submit()} disabled={buttonDisable}>Submit
105 |
106 |
107 |
108 | );
109 | }
110 |
111 | export default Submit;
112 |
--------------------------------------------------------------------------------
/Frontend/src/page/ShowProblem.js:
--------------------------------------------------------------------------------
1 |
2 | import React, { useEffect, useState } from 'react';
3 |
4 | import { Link, useHistory, useLocation } from 'react-router-dom';
5 | // import queryString from 'query-string'
6 | import {BACKEND_URL} from '../util/config'
7 | import ReactLoading from 'react-loading';
8 |
9 | function ShowProblem(props) {
10 |
11 | // const query = queryString.parse(useLocation().search)
12 | const id = props.match.params.id
13 | const history = useHistory()
14 | const [loading, setLoading] = useState(true)
15 | const [title, setTitle] = useState(null)
16 | const [time_limit, setTimeLimit] = useState(null)
17 | const [memory_limit, setMemoryLimit] = useState(null)
18 | const [content, setContent] = useState(null)
19 | const [author, setAuthor] = useState(null)
20 | const [success, setSuccess] = useState(null)
21 | const [attempt, setAttempt] = useState(null)
22 | // time_limit: timeLimit, memory_limit: memoryLimit, content, title, author
23 |
24 | useEffect(()=>{
25 |
26 | try{
27 |
28 | (async()=>{
29 |
30 | const response = await fetch( `${BACKEND_URL}/problem?id=${id}`, {
31 | credentials: 'include',
32 | headers: {
33 | 'Content-Type': 'application/json'
34 | },
35 | })
36 | const problem_data = await response.json()
37 | //console.log(problem_data)
38 | if(problem_data.success === "yes") {
39 | setContent(problem_data.data.content)
40 | setTitle(problem_data.data.title)
41 | setMemoryLimit(problem_data.data.memory_limit)
42 | setTimeLimit(problem_data.data.time_limit)
43 | setAuthor(problem_data.data.author)
44 | setSuccess(problem_data.data.success)
45 | setAttempt(problem_data.data.attempt)
46 | setLoading(false)
47 | } else {
48 | history.push('/404')
49 | // history.go(0)
50 | }
51 |
52 | })()
53 |
54 | }catch(err){
55 | //history.push('/404')
56 | }
57 |
58 |
59 | }, [])
60 |
61 | const submit = ()=> {
62 | history.push(`/submit/${id}`)
63 | // history.go(0)
64 | }
65 |
66 | return (
67 |
68 | {loading &&
69 |
70 |
71 |
72 | }
73 | {!loading &&
74 |
75 |
76 | ID
77 | Time Limit (s)
78 | Memory Limit (MB)
79 | Success / Attempt
80 | Author
81 | Action
82 |
83 |
84 | {id}
85 | {time_limit}
86 | {memory_limit}
87 | {success} / {attempt}
88 | {author}
89 | Submit
90 |
91 |
92 |
93 |
94 |
95 | {title}
96 |
97 |
98 |
99 |
100 | {/*
101 |
104 |
*/}
105 |
}
106 |
107 | );
108 | }
109 |
110 | export default ShowProblem;
111 |
--------------------------------------------------------------------------------
/Frontend/src/page/AdminUser.js:
--------------------------------------------------------------------------------
1 |
2 | import React, {useEffect, useState} from 'react';
3 |
4 | import queryString from 'query-string'
5 | import { Link, useLocation, useHistory } from 'react-router-dom'
6 | import ReactLoading from 'react-loading';
7 | import {BACKEND_URL, PAGINATION} from '../util/config'
8 |
9 | function AdminUser() {
10 |
11 | const query = queryString.parse(useLocation().search)
12 | const history = useHistory()
13 | const [page, setPage] = useState( Math.max(query.page || 1), 1 )
14 | const [userData, setUserData] = useState(null)
15 |
16 | const handleEnter = (event) => {
17 | if(event.key === 'Enter') goto()
18 | }
19 |
20 |
21 | useEffect( () => {
22 |
23 | (async () => {
24 | try{
25 | console.log('fetching', `${BACKEND_URL}/user/all?page=${page}`)
26 | const response2 = await fetch( `${BACKEND_URL}/user/all?page=${page}`, {
27 | credentials: 'include',
28 | headers: {
29 | 'Content-Type': 'application/json'
30 | },
31 | })
32 | const user_data = await response2.json()
33 |
34 | if(user_data.success === "yes") {
35 | setUserData(user_data.data)
36 | } else{
37 | throw new Error('Not found')
38 | }
39 | } catch(err){
40 | console.log(err)
41 | }
42 |
43 | })();
44 | }, [])
45 |
46 | const set_page = (value) => {
47 | if(value >= 1) setPage(value)
48 | }
49 |
50 | const goto = () => {
51 | const data = queryString.stringify({
52 | page,
53 | })
54 | history.push(`/admin/user?${data}`)
55 | history.go(0)
56 | }
57 |
58 |
59 | return (
60 |
61 | {!userData &&
62 |
63 |
64 |
65 | }
66 | {userData &&
67 |
68 |
69 | Users
70 |
71 |
72 |
73 | Page
74 | handleEnter(e)} onChange={e => set_page( parseInt(e.target.value.trim())) }/>
75 | ↣
76 |
77 |
78 |
79 |
80 |
81 |
82 | No.
83 | Username
84 | Fullname
85 | Email
86 | Role
87 | disable
88 | createdAt
89 |
90 |
91 |
92 | { userData.map((user, index) => {
93 | return (
94 |
95 | {index + 1 + ((query.page || 1) - 1) * PAGINATION}
96 | {user.username}
97 | {user.fullname}
98 | {user.email}
99 | {user?.role?.join(', ')}
100 | {user.disable ? "True": "False"}
101 | {user.createdAt}
102 |
103 | )
104 | }) }
105 |
106 |
107 |
108 |
109 |
110 |
111 | }
112 |
113 |
114 | );
115 | }
116 |
117 | export default AdminUser;
118 |
--------------------------------------------------------------------------------
/Frontend/src/page/Submission.js:
--------------------------------------------------------------------------------
1 |
2 | import React, { useEffect, useState } from 'react';
3 |
4 |
5 | import ReactLoading from 'react-loading';
6 | import { Link, useHistory } from 'react-router-dom';
7 |
8 | import {BACKEND_URL} from '../util/config'
9 | import AceEditor from "react-ace";
10 |
11 | function Submission(props) {
12 |
13 | const id = props.match.params.id
14 | const [submissionData, setSubmissionData] = useState(null)
15 | const [error, setError] = useState(false)
16 |
17 | const history = useHistory()
18 |
19 |
20 | const verdictStyle = (verdict)=> {
21 | if(verdict === "Accepted") return " text-green-600"
22 | else if(verdict === "Not Judged Yet") return ""
23 | else return " text-red-600"
24 | }
25 |
26 |
27 | useEffect(() => {
28 |
29 | try{
30 |
31 | (async()=>{
32 |
33 | const response = await fetch( `${BACKEND_URL}/submission?id=${id}`, {
34 | credentials: 'include',
35 | headers: {
36 | 'Content-Type': 'application/json'
37 | },
38 | })
39 | const data = await response.json();
40 |
41 | //console.log(data)
42 | if(data.success === "yes") {
43 | setSubmissionData(data.data)
44 | } else if(data.message === "Solve the problem first"){
45 | setError(true)
46 | } else {
47 | history.push('/404')
48 | //setError(true)
49 | }
50 |
51 | })()
52 |
53 | } catch(err){
54 | //console.log(err)
55 | }
56 |
57 |
58 | }, [])
59 |
60 | return (
61 |
62 | {(!submissionData && !error) &&
63 |
64 |
65 |
66 | }
67 | {(submissionData && !error) &&
68 |
71 |
72 |
73 |
74 |
75 | ID
76 | Problem ID
77 | Problem Title
78 | Username
79 | Language
80 | Verdict
81 | Time
82 |
83 |
84 | {submissionData.id}
85 | {submissionData.problem_id}
86 | {submissionData["Problem.title"]}
87 | {submissionData.username}
88 | {submissionData.language}
89 | {submissionData.verdict}
90 | {new Date(submissionData.createdAt).toLocaleString()}
91 |
92 |
93 |
94 |
95 |
113 |
114 | }
115 | {error &&
116 |
117 |
Submission cannot be shown. Please solve the problem first.
118 |
119 |
120 |
Happy Coding!
121 |
122 |
}
123 |
124 | );
125 | }
126 |
127 | export default Submission;
128 |
--------------------------------------------------------------------------------
/Frontend/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.0/8 are considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl, {
104 | headers: { 'Service-Worker': 'script' },
105 | })
106 | .then(response => {
107 | // Ensure service worker exists, and that we really are getting a JS file.
108 | const contentType = response.headers.get('content-type');
109 | if (
110 | response.status === 404 ||
111 | (contentType != null && contentType.indexOf('javascript') === -1)
112 | ) {
113 | // No service worker found. Probably a different app. Reload the page.
114 | navigator.serviceWorker.ready.then(registration => {
115 | registration.unregister().then(() => {
116 | window.location.reload();
117 | });
118 | });
119 | } else {
120 | // Service worker found. Proceed as normal.
121 | registerValidSW(swUrl, config);
122 | }
123 | })
124 | .catch(() => {
125 | console.log(
126 | 'No internet connection found. App is running in offline mode.'
127 | );
128 | });
129 | }
130 |
131 | export function unregister() {
132 | if ('serviceWorker' in navigator) {
133 | navigator.serviceWorker.ready
134 | .then(registration => {
135 | registration.unregister();
136 | })
137 | .catch(error => {
138 | console.error(error.message);
139 | });
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/controller/judge/Runner.js:
--------------------------------------------------------------------------------
1 | const {writeFileSync, unlinkSync} = require('fs')
2 | const {spawn, execFile} = require('child_process')
3 | const {Readable} = require("stream");
4 | const path = require('path')
5 | module.exports = class Runner {
6 |
7 | /**
8 | *
9 | * @param {String} souce_code | text string containing source code
10 | * @param {String} extension | enum {"cpp"} currently
11 | * @param {String} input
12 | * @param {Number} timelimit | milliseconds
13 | * @param {Number} IMMUNE_TIME_BONUS | milliseconds
14 | * @param {String} TEST_DIRECTORY | default 'TEST'
15 | * @param {Number} MAX_TIME_LIMIT | default 3000 milliseconds
16 | */
17 |
18 |
19 | constructor(souce_code, extension, input, timelimit, IMMUNE_TIME_BONUS = 100, TEST_DIRECTORY = __dirname, MAX_TIME_LIMIT = 3000) {
20 | this.souce_code = souce_code
21 | this.extension = extension
22 | this.input = input
23 | this.timelimit = timelimit
24 | this.IMMUNE_TIME_BONUS = IMMUNE_TIME_BONUS
25 | this.FILENAME = `${this.randomstring()}${Date.now()}${this.randomstring()}` // highly likely to be unique i guess
26 | this.TEST_DIRECTORY = TEST_DIRECTORY
27 | this.MAX_TIME_LIMIT = MAX_TIME_LIMIT
28 | this.COMPILED_FILE_PATH = path.join(this.TEST_DIRECTORY, `${this.FILENAME}.out`)
29 | this.SOURCE_FILE_PATH = path.join(this.TEST_DIRECTORY, `${this.FILENAME}.cpp`)
30 | }
31 |
32 | randomstring(size=5) {
33 | let str = "";
34 | for(let i = 0; i < 5; i++){
35 | str += "0123456789"[ Math.floor(Math.random() * 10) ]
36 | }
37 | return str;
38 | }
39 |
40 | compile() {
41 |
42 | return new Promise((resolve, reject) => {
43 | this.save_file(this.SOURCE_FILE_PATH)
44 | if(this.extension === 'cpp') {
45 | const compiler = spawn('g++', [`${this.SOURCE_FILE_PATH}`, '-o',`${this.COMPILED_FILE_PATH}`]);
46 | compiler.stderr.on('data', (data) => {
47 | reject();
48 | });
49 | compiler.on('close', (data) => {
50 | if(data === 0)
51 | resolve()
52 | else
53 | resolve()
54 | });
55 | } else {
56 | resolve(true)
57 | }
58 | })
59 | }
60 |
61 | save_file(filepath) {
62 | try{
63 | writeFileSync(filepath, this.souce_code);
64 | } catch(err){
65 | // console.log(err.message)
66 | }
67 | }
68 |
69 | delete_file(file){
70 | try{
71 | unlinkSync(file)
72 | } catch(err){
73 | // console.log(err.message)
74 | }
75 | }
76 |
77 | execute() {
78 | //console.log('came 1')
79 | return new Promise((resolve, reject) => {
80 | try{ //console.log('came 2')
81 | let executor = null, start_time = Date.now();
82 | executor = execFile(this.COMPILED_FILE_PATH, {stdio: ['inherit', 'inherit', 'inherit', 'ipc']}); //console.log('came 3')
83 | this.over_the_time_limit = false;
84 | const timeout = setTimeout(() => {
85 | this.over_the_time_limit = true;
86 | try{
87 | executor.stdin.pause()
88 | executor.kill()
89 | } catch(err){
90 |
91 | }
92 | }, Math.max(this.timelimit, this.MAX_TIME_LIMIT))
93 | let output = "";
94 |
95 |
96 | executor.stdout.on('data', (data) => {
97 | output += data
98 | });
99 |
100 | executor.stderr.on('data', (data) => {
101 | clearTimeout(timeout)
102 | reject( new Error("Runtime Error") )
103 | });
104 |
105 | executor.on('close', code => {
106 | clearTimeout(timeout)
107 | if(this.over_the_time_limit) {
108 | reject( new Error("Time Limit Exceeded") )
109 | }
110 | if(code === null) {
111 | reject( new Error("Memory Limit Exceeded") )
112 | } else if(code === 0) {
113 | resolve( [String(output), Date.now() - start_time])
114 | } else {
115 | reject( new Error("Runtime Error") );
116 | }
117 | })
118 | const stdinStream = new Readable();
119 | stdinStream.push(this.input);
120 | stdinStream.push(null);
121 | stdinStream.pipe(executor.stdin).on('error', (err) => {
122 | // no need to push
123 | // console.log(err)
124 | });
125 |
126 | } catch(err) {
127 | reject( new Error("Runtime Error") )
128 | }
129 | })
130 | }
131 |
132 | run() {
133 |
134 | return new Promise((resolve, reject) => {
135 | let compiled = false;
136 | this.compile()
137 | .then(() => { //console.log('compiled')
138 | compiled = true
139 | })
140 | .catch(() => { //console.log('compiled error')
141 | throw new Error('Compilation Error')
142 | })
143 | .then(() => {
144 | return this.execute()
145 | })
146 | .then(output => { //console.log('executed')// [output, timelimit]
147 | this.check_for_immunity(output[1])
148 | resolve({
149 | success: true,
150 | output: output[0],
151 | error: null,
152 | time: output[1]
153 | })
154 | })
155 | .catch(err => { //console.log('dont forget me', err)
156 | reject({
157 | success: false,
158 | output: null,
159 | error: (err && err.message) ? err.message : "Runtime Error",
160 | time: null
161 | })
162 | })
163 | .finally(() => { //console.log('here')
164 | try{
165 | this.delete_file(`${this.SOURCE_FILE_PATH}`)
166 | if(this.extension === 'cpp' && compiled) this.delete_file(`${this.COMPILED_FILE_PATH}`)
167 | } catch(err){
168 | // hehe
169 | }
170 | })
171 | })
172 | }
173 |
174 | check_for_immunity(time) {
175 | if(Math.min(this.MAX_TIME_LIMIT, this.timelimit) + this.IMMUNE_TIME_BONUS < time) throw new Error("Time Limit Exceeded")
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/controller/user/user.controller.js:
--------------------------------------------------------------------------------
1 | const {SUCCESS, FAILURE, STATUS} = require('../../api_response')
2 | const {User, Submission, Problem} = require('../../models')
3 | const sequelize = require('sequelize')
4 |
5 | const {verify} = require('../../Service/jwt')
6 |
7 |
8 | const get_user_by_username = async(username, RAW=false) => {
9 | return await User.findOne({
10 | where: {
11 | username,
12 | },
13 | raw: RAW
14 | })
15 | }
16 |
17 | module.exports = {
18 | upsert_user: async (profile) => {
19 | const data = {}
20 | data.email = profile.emails[0].value;
21 | let user = await User.findOne({
22 | where: {
23 | email: data.email
24 | }
25 | })
26 | if(user){
27 | return user
28 | }
29 | data.fullname = profile.displayName;
30 | if(profile.photos && profile.photos.length >= 0) data.image = profile.photos[0].value;
31 | data.username = "";
32 | data.fullname.split(' ').forEach(part => {
33 | data.username += part.toLowerCase()
34 | })
35 | if(profile.role) data.role = profile.role;
36 | while(true) {
37 | if(await User.findOne({
38 | where: {
39 | username: data.username
40 | }
41 | })) {
42 | data.username += "0123456789"[ Math.floor(Math.random() * "0123456789".length) ]
43 | } else{
44 | break;
45 | }
46 | }
47 | user = await User.create(data);
48 | return user;
49 | },
50 |
51 | get_user_by_username: async (username) => {
52 | return await get_user_by_username(username)
53 | },
54 |
55 | get_user: async(req, res) => { //console.log('cookie', req.cookies.glaxoj, 'verified', verify(req.cookies.glaxoj))
56 | const username = req.query.username || verify(req.cookies.glaxoj)
57 | // console.log('query', req.query.username)
58 | // console.log('session', req.session.username)
59 | // console.log('username', username)
60 | if(!username){
61 | res.status(STATUS.NOT_FOUND).send(FAILURE("\"username\" required"))
62 | return;
63 | }
64 | const user = await get_user_by_username(username, true);
65 | if(!user) res.status(STATUS.NOT_FOUND).send(FAILURE("\"username\" not found"))
66 | else {
67 |
68 | // all solved problems
69 | user.solved_problem = (await Submission.aggregate('problem_id', 'DISTINCT', {
70 | where: {
71 | username: user.username,
72 | verdict: 'Accepted'
73 | },
74 | plain: false,
75 | include: [{
76 | model: Problem,
77 | attributes: ['title']
78 | }]
79 | })).map(each => {
80 | return {id: each["Problem.id"], title: each["Problem.title"]}
81 | })
82 | //
83 | // all unsolved problems
84 | user.unsolved_problem = (await Submission.aggregate('problem_id', 'DISTINCT', {
85 | where: {
86 | username: user.username,
87 | verdict: {
88 | [sequelize.Op.not]: 'Accepted'
89 | }
90 | },
91 | plain: false,
92 | include: [{
93 | model: Problem,
94 | attributes: ['title']
95 | }]
96 | })).map(each => {
97 | return {id: each["Problem.id"], title: each["Problem.title"]}
98 | }).filter(each => {
99 | for(let i = 0; i < user.solved_problem.length; i++){
100 | if(user.solved_problem[i].id === each.id) return false
101 | }
102 | return true
103 | })
104 | // total submission
105 | user.total_submission = await Submission.count({
106 | where: {
107 | username: user.username
108 | }
109 | })
110 | if(verify(req.cookies.glaxoj) !== user.username) delete user.email
111 | res.send( SUCCESS(user) )
112 | }
113 | },
114 |
115 | // set_session_temporary: async(req, res) => { // temporary
116 | // req.session.username = req.query.username
117 | // res.send("done")
118 | // },
119 |
120 | update_user: async(req, res) => { //console.log(req.query, req.query.disable, req.query.admin)
121 | if(!req.query.username) res.status(STATUS.NOT_FOUND).send(FAILURE("\"username\" not found"))
122 | if(req.query.disable === undefined && req.query.admin === undefined) res.status(STATUS.NOT_FOUND).send(FAILURE("\"disable\" or \"admin\" not found"))
123 | else {
124 | const user = await get_user_by_username(req.query.username);
125 | if(!user) res.status(STATUS.NOT_FOUND).send(FAILURE("\"user\" not found"))
126 | else {
127 | // if(req.body.disable) user.disable = req.body.disable
128 | // if(req.body.role) user.role = req.body.role;
129 | // await user.save();
130 | // res.send( SUCCESS(user) )
131 | if(req.query.disable === 'true') user.disable = true
132 | else if(req.query.disable === 'false') user.disable = false
133 | if(req.query.admin === 'true' && user.email !== process.env.ADMIN_EMAIL) {
134 | user.role = ['ADMIN']
135 | } else if(req.query.admin === 'false' && user.email !== process.env.ADMIN_EMAIL){
136 | user.role = []
137 | }
138 | res.send( SUCCESS(await user.save()) )
139 | }
140 | }
141 | },
142 |
143 | get_all_user: async(req, res) => {
144 |
145 | const page = parseInt(req.query.page) || 1;
146 | const limit = parseInt( process.env.PAGINATION )
147 | const offset = limit * (page - 1)
148 |
149 | res.send( SUCCESS( await User.findAll({
150 | limit,
151 | offset
152 | }) ) )
153 | },
154 |
155 | count_user: async(req, res) => {
156 | res.send( SUCCESS( await User.count({
157 | }) ) )
158 | },
159 |
160 | nothing: async(req, res) => {
161 | res.send( SUCCESS() )
162 | },
163 |
164 | logout: async(req, res) => {
165 | let options = {
166 | maxAge: 1000 * 60 * 60 * 24 * 7, // would expire after 7 days
167 | // secure: true,
168 | sameSite: 'none',
169 | // domain: 'herokuapp.com',
170 | // httpOnly: false, // The cookie only accessible by the web server
171 | path: "/",
172 | secure: true,
173 | //domain: ".herokuapp.com", REMOVE THIS HELPED ME (I dont use a domain anymore)
174 | httpOnly: true
175 | }
176 | res.clearCookie("glaxoj", options);
177 | res.send(SUCCESS())
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/Frontend/src/page/Admin.js:
--------------------------------------------------------------------------------
1 |
2 | import React, {useState, useEffect} from 'react';
3 |
4 | import { Link, useHistory } from 'react-router-dom'
5 | import ReactLoading from 'react-loading';
6 |
7 | import {BACKEND_URL} from '../util/config'
8 |
9 |
10 | function Admin() {
11 |
12 | const history = useHistory()
13 | const [judge, setJudge] = useState("sex")
14 |
15 | const [userCount, setUserCount] = useState("loading...")
16 | const [submissionCount, setSubmissionCount] = useState("loading...")
17 | const [deleteState, setDeleteState] = useState("NILL")
18 | const [deleteID, setDeleteID] = useState(null)
19 |
20 | useEffect(() => {
21 |
22 | (async() => {
23 |
24 | try{
25 | const response = await fetch( `${BACKEND_URL}/submission/judge_status`, {
26 | credentials: 'include',
27 | headers: {
28 | 'Content-Type': 'application/json'
29 | },
30 | })
31 | const user_data = await response.json()
32 |
33 | if(user_data.success === "yes") {
34 | setJudge(user_data.data)
35 | } else{
36 | throw new Error('error')
37 | }
38 |
39 | const extra_responses = await Promise.all([
40 | fetch( `${BACKEND_URL}/user/count`, {
41 | credentials: 'include',
42 | headers: {
43 | 'Content-Type': 'application/json'
44 | },
45 | }),
46 | fetch( `${BACKEND_URL}/submission/count`, {
47 | credentials: 'include',
48 | headers: {
49 | 'Content-Type': 'application/json'
50 | },
51 | }),
52 | ])
53 |
54 | extra_responses[0] = await extra_responses[0].json();
55 | extra_responses[1] = await extra_responses[1].json();
56 |
57 | // console.log(extra_responses)
58 | if(extra_responses[0].success === "yes") setUserCount(extra_responses[0].data)
59 | if(extra_responses[1].success === "yes") setSubmissionCount(extra_responses[1].data)
60 |
61 | } catch(err) {
62 | history.push('/404')
63 | }
64 |
65 | })()
66 |
67 | }, [])
68 |
69 | const toggle = async() => {
70 | try{
71 | const response = await fetch( `${BACKEND_URL}/submission/judge_toggle`, {
72 | credentials: 'include',
73 | method: 'POST',
74 | headers: {
75 | 'Content-Type': 'application/json'
76 | },
77 | })
78 | const user_data = await response.json()
79 |
80 | if(user_data.success === "yes") {
81 | setJudge(user_data.data)
82 | } else{
83 | throw new Error(user_data.message)
84 | }
85 | } catch(err) {
86 | console.log(err)
87 | }
88 | }
89 |
90 | const deleteThat = async () => {
91 | if(!deleteID) return;
92 | setDeleteState("Loading...")
93 | try{
94 | const response = await fetch( `${BACKEND_URL}/submission?id=${deleteID}`, {
95 | credentials: 'include',
96 | method: 'DELETE',
97 | headers: {
98 | 'Content-Type': 'application/json'
99 | },
100 | })
101 | const data = await response.json()
102 | // console.log(data)
103 | if(data.success === "yes") {
104 | setDeleteState("Success")
105 | } else {
106 | setDeleteState("Fail")
107 | }
108 | } catch(err) {
109 | setDeleteState("Fail")
110 | }
111 | }
112 |
113 | return (
114 |
115 | {judge === "sex" &&
116 |
117 |
}
118 | {judge !== "sex" &&
119 |
120 | Admin Panel
121 |
122 |
123 | Problems
124 | Create Problem
125 | User
126 | Turn {judge ? 'Off' : 'On'} Judge
127 |
128 |
129 | Information
130 |
131 |
132 |
133 |
134 | Users
135 | {userCount}
136 |
137 |
138 | Submissions
139 | {submissionCount}
140 |
141 |
142 |
143 |
144 |
145 |
146 | Delete Submission
147 |
148 |
162 |
163 |
}
164 |
165 | );
166 | }
167 |
168 | export default Admin;
169 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const app = express()
3 |
4 | const dotenv = require('dotenv')
5 | dotenv.config()
6 |
7 |
8 |
9 |
10 | const cors = require('cors')
11 |
12 | const FRONTEND_URL = process.env.NODE_ENV == 'production' ? process.env.FRONTEND_URL : 'http://localhost:3001'
13 |
14 | app.use( cors({
15 | origin: FRONTEND_URL,
16 | credentials: true
17 | }) )
18 |
19 | const rateLimit = require("express-rate-limit");
20 |
21 | // app.set('trust proxy', 1);
22 |
23 | const limiter = rateLimit({
24 | windowMs: 15 * 60 * 1000, // 15 minutes
25 | max: 100 // limit each IP to 100 requests per windowMs
26 | });
27 |
28 | // apply to all requests
29 | if(process.env.NODE_ENV === "production") app.use(limiter);
30 |
31 |
32 | app.get('/', (req, res) => {
33 | res.send({
34 | "success": "fine",
35 | "yes": "no"
36 | })
37 | })
38 |
39 | app.use(express.json({
40 | limit: '50mb'
41 | }));
42 |
43 | app.use(express.urlencoded({
44 | limit: '50mb',
45 | parameterLimit: 100000,
46 | extended: true
47 | }));
48 |
49 |
50 | // dirty dirty dirty
51 |
52 | const passport = require("passport");
53 | const GoogleStrategy = require("passport-google-oauth20").Strategy;
54 |
55 | app.use(require('cookie-parser')());
56 | app.use(require('body-parser').urlencoded({ extended: true }));
57 | // const session = require('express-session')
58 | // app.use(session({
59 | // // store: new (require('connect-pg-simple')(session))({
60 | // // createTableIfMissing: true
61 | // // }),
62 | // secret: process.env.SESSION_SECRET,
63 | // resave: true,
64 | // saveUninitialized: true,
65 | // cookie: { secure: !true }
66 | // }));
67 | app.use(passport.initialize());
68 | // app.use(passport.session());
69 |
70 | const {upsert_user, get_user_by_username} = require('./controller/user/user.controller')
71 |
72 | passport.use(new GoogleStrategy({
73 | clientID: process.env.GOOGLE_CLIENT_ID,
74 | clientSecret: process.env.GOOGLE_CLIENT_SECRET,
75 | callbackURL: "/auth/google/redirect"
76 | },
77 | async (accessToken, refreshToken, profile, done) => {
78 | const user = await upsert_user(profile)
79 | done(null, user)
80 | }));
81 |
82 | passport.serializeUser((user, done) => {
83 | done(null, user.username);
84 | });
85 |
86 | passport.deserializeUser(async (username, done) => {
87 | done(null, get_user_by_username(username))
88 | });
89 |
90 |
91 | app.get("/auth/google", passport.authenticate("google", {
92 | scope: ["profile", "email"]
93 | }));
94 |
95 | const google_redirect_url = "/auth/google/redirect"
96 |
97 | // app.get(google_redirect_url, passport.authenticate('google'), (req, res) => {
98 | // try{
99 | // console.log(req.session)
100 | // req.session.username = req.session.passport.user
101 | // let options = {
102 | // maxAge: 1000 * 60 * 15, // would expire after 15 minutes
103 | // httpOnly: true, // The cookie only accessible by the web server
104 | // signed: true // Indicates if the cookie should be signed
105 | // }
106 |
107 | // // Set cookie
108 | // res.cookie('cookieName', 'cookieValue', options)
109 | // console.log(res.cookie)
110 | // console.log('sex')
111 | // }catch(err){
112 |
113 | // }
114 | // // res.redirect(process.env.FRONTEND_URL)
115 | // res.send({sex: 'sex'})
116 | // });
117 |
118 | const {sign} = require('./Service/jwt')
119 |
120 | const crypto = require("crypto")
121 | const sexy_session = {}
122 |
123 |
124 | app.get(google_redirect_url, passport.authenticate('google'), (req, res) => {
125 | // console.log(req.session.passport.user)
126 | const username = req.session.passport.user
127 |
128 |
129 | let poor_red = "";
130 |
131 | while(true){
132 | // poor_red = Math.random().toString(36).substr(10);
133 | poor_red = crypto.randomBytes(20).toString('hex')
134 | if(!sexy_session[poor_red]) break;
135 | }
136 |
137 | sexy_session[poor_red] = sign(username)
138 |
139 | // console.log('username', username)
140 | // // console.log(sign(username))
141 |
142 | let options = {
143 | maxAge: 1000 * 60 * 60 * 24 * 7, // would expire after 7 days
144 | // secure: true,
145 | sameSite: 'none',
146 | // domain: 'herokuapp.com',
147 | // httpOnly: false, // The cookie only accessible by the web server
148 | path: "/",
149 | secure: true,
150 | //domain: ".herokuapp.com", REMOVE THIS HELPED ME (I dont use a domain anymore)
151 | httpOnly: true
152 | }
153 |
154 | // console.log('cookie setting', sign(username))
155 |
156 | // // Set cookie
157 | res.cookie('glaxoj', sign(username), options)
158 |
159 | // console.log('res > ', res.cookies)
160 |
161 | //manually
162 | // res.setHeader('set-cookie', [`glaxoj=${sign(username)}; Domain=.herokuapp.com; sameSite: none;`])
163 |
164 | // res.send({s: 's'})
165 | res.redirect( ( process.env.NODE_ENV == 'production' ? process.env.FRONTEND_URL : 'http://localhost:3001') + `/auth?token=${poor_red}`)
166 | // res.redirect('/verify')
167 | });
168 |
169 | app.post('/auth', (req, res) => {
170 | // res.redirect(302, process.env.NODE_ENV == 'production' ? process.env.FRONTEND_URL : 'http://localhost:3001')
171 | const token = req.body.token;
172 | try{ //console.log(token)
173 | const cookie = sexy_session[token]
174 | if(!cookie) throw Error('no token')
175 | // console.log(cookie, sexy_session, token)
176 | let options = {
177 | maxAge: 1000 * 60 * 60 * 24 * 7, // would expire after 7 days
178 | // secure: true,
179 | sameSite: 'none',
180 | // domain: 'herokuapp.com',
181 | // httpOnly: false, // The cookie only accessible by the web server
182 | path: "/",
183 | secure: true,
184 | //domain: ".herokuapp.com", REMOVE THIS HELPED ME (I dont use a domain anymore)
185 | httpOnly: true
186 | }
187 | res.cookie('glaxoj', cookie, options)
188 | delete sexy_session[token]
189 | res.send( SUCCESS(cookie) )
190 |
191 | } catch(err) {
192 | console.log(err)
193 | res.send( FAILURE() )
194 | }
195 |
196 | })
197 |
198 |
199 |
200 | const routes = require('./routes')
201 |
202 | app.use(routes)
203 |
204 | const db = require('./models')
205 |
206 | const {start_judge_queue} = require('./controller/judge')
207 | const { SUCCESS, FAILURE } = require('./api_response')
208 | start_judge_queue(); // start judge queue at the beginning
209 |
210 |
211 | const {ektu_chalak_function} = require('./controller/submission/submission.controller')
212 |
213 | db.sequelize.sync({ alter: true }).then(async(_) => {
214 | console.log('database connected')
215 |
216 | // one temporary thing
217 | ektu_chalak_function()
218 |
219 |
220 | // adding one default admin if not there
221 | await upsert_user({
222 | displayName: process.env.ADMIN_NAME,
223 | emails: [ { value: process.env.ADMIN_EMAIL } ],
224 | photos: [
225 | {
226 | value: process.env.ADMIN_PICTURE
227 | }
228 | ],
229 | role: ['ADMIN']
230 | })
231 |
232 | app.listen( process.env.PORT || 3000 , () => { // fuck you heroku
233 | console.log(`server running`)
234 | })
235 | })
236 |
237 |
238 | // app.listen(process.env.PORT, () => {
239 | // console.log('yes')
240 | // })
241 |
--------------------------------------------------------------------------------
/controller/problem/problem.controller.js:
--------------------------------------------------------------------------------
1 |
2 | const {SUCCESS, FAILURE, STATUS} = require('../../api_response')
3 | const {Problem, Submission, sequelize, Sequelize} = require('../../models')
4 | // const sequelize = require('sequelize')
5 | const {Op} = require('sequelize')
6 |
7 | // masks
8 | const mask = {
9 | no_inputs_outputs: ['id', 'tag', 'title', 'content', 'author', 'time_limit', 'memory_limit', 'createdAt', 'updatedAt'],
10 | admin: ['id', 'tag', 'title', 'content', 'author', 'time_limit','inputs', 'outputs', 'memory_limit', 'disable', 'createdAt', 'updatedAt'],
11 | search_result: ['id', 'tag', 'title', 'author', 'time_limit', 'memory_limit', 'createdAt', 'updatedAt'],
12 | admin_search: ['id', 'tag', 'title', 'author', 'time_limit', 'memory_limit', 'disable', 'createdAt', 'updatedAt'],
13 | }
14 |
15 | const create_problem = async (data, user) => {
16 | try{
17 | const problem = await Problem.create(data);
18 | return {problem};
19 | } catch(err) {
20 | return {
21 | status: STATUS.BAD_REQUEST,
22 | message: err.message,
23 | failed: true
24 | }
25 | }
26 | }
27 |
28 | const update_problem = async (query, data, user) => {
29 | try{
30 | let problem = await Problem.findOne({
31 | where: {
32 | id: query.id
33 | }
34 | });
35 | if(!problem) throw new Error("\"problem\" not found");
36 | Object.keys(data).forEach(key => {
37 | problem[key] = data[key]
38 | })
39 | problem = await problem.save();
40 | return {problem};
41 | } catch(err) {
42 | return {
43 | status: STATUS.BAD_REQUEST,
44 | message: err.message,
45 | failed: true
46 | }
47 | }
48 | }
49 |
50 | const get_problem = async(query, user) => {
51 | try{
52 |
53 | let MASK = mask.no_inputs_outputs;
54 | let query_object = {id: query.id, disable: false}
55 |
56 | // console.log(user)
57 | if(query.admin && user.role.indexOf('ADMIN') !== -1) {
58 | MASK = mask.admin
59 | delete query_object.disable
60 | }
61 |
62 | let problem = await Problem.findOne({
63 | where: query_object,
64 | attributes: [...MASK,
65 | [sequelize.literal('(SELECT COUNT(*) FROM "Submissions" WHERE "Submissions"."problem_id" = "Problem"."id")'), 'attempt'],
66 | [sequelize.literal(`(SELECT COUNT(*) FROM "Submissions" WHERE "Submissions"."problem_id" = "Problem"."id" AND "Submissions"."verdict" = '${"Accepted"}')`), 'success']],
67 |
68 | });
69 |
70 | if(!problem) throw new Error("\"problem\" not found");
71 |
72 | return {problem};
73 |
74 | } catch(err) {
75 | return {
76 | status: STATUS.BAD_REQUEST,
77 | message: err.message,
78 | failed: true
79 | }
80 | }
81 | }
82 |
83 |
84 | const search_problem = async (query, user) => {
85 | try{
86 | let search_object = {}
87 | let MASK = mask.search_result
88 |
89 | if(query.tag) {
90 | search_object = {
91 | ...search_object,
92 | tag: {
93 | [Op.contains]: [query.tag]
94 | }
95 | }
96 | }
97 | if(query.author) {
98 | search_object = {
99 | ...search_object,
100 | author: {
101 | [Op.iLike]: '%' + query.author + '%'
102 | }
103 | }
104 | }
105 | if(query.title) {
106 | search_object = {
107 | ...search_object,
108 | title: {
109 | [Op.iLike]: '%' + query.title + '%'
110 | }
111 | }
112 | }
113 |
114 | if(!(query.admin && user.role.indexOf('ADMIN') !== -1)) {
115 | search_object = {
116 | ...search_object,
117 | disable: false
118 | }
119 | } else {
120 | MASK = mask.admin_search
121 | }
122 |
123 | const page = parseInt(query.page) | 1
124 | const limit = parseInt( process.env.PAGINATION )
125 | const offset = limit * (page - 1)
126 |
127 |
128 | const problems = await Problem.findAll({
129 | where: search_object,
130 | // attributes: [...MASK, [Sequelize.literal('(SELECT COUNT(*) FROM Submissions WHERE Submissions.problem_id = "Problem"."id")'), 'PostCount']],
131 | // include: [{
132 | // model: Submission
133 | // }],
134 | // group: ['Problem.id', 'Submission.id'],
135 | // attributes: [
136 | // ...MASK
137 | // ], // add any attributes if u need and make a subquery
138 | // include: [{
139 | // model: Submission,
140 | // attributes: [sequelize.fn('COUNT',sequelize.col('Submission.id')),'ANY_NAME_']
141 | // }], // just adding an association
142 | // // group: ['Problem.id', 'Submissions.id'], // add group_by model id and any association id
143 | attributes: [
144 | ...MASK,
145 | [sequelize.literal('(SELECT COUNT(*) FROM "Submissions" WHERE "Submissions"."problem_id" = "Problem"."id")'), 'attempt'],
146 | [sequelize.literal(`(SELECT COUNT(*) FROM "Submissions" WHERE "Submissions"."problem_id" = "Problem"."id" AND "Submissions"."verdict" = '${"Accepted"}')`), 'success']
147 | ],
148 | order: sequelize.literal('"id" ASC'),
149 | limit,
150 | offset,
151 | })
152 |
153 | return {problems}
154 | } catch(err) {
155 | return {
156 | status: STATUS.BAD_REQUEST,
157 | message: err.message,
158 | failed: true
159 | }
160 | }
161 | }
162 |
163 |
164 | const delete_problem = async(query) => {
165 | try{
166 | const problem = await Problem.destroy({
167 | where: {
168 | id : query.id
169 | }
170 | })
171 |
172 | return {problem}
173 |
174 | } catch(err) {
175 | return {
176 | status: STATUS.BAD_REQUEST,
177 | message: err.message,
178 | failed: true
179 | }
180 | }
181 | }
182 |
183 | module.exports = {
184 |
185 | create_problem: async(req, res) => {
186 | const result = await create_problem(req.body);
187 | if(result.failed) {
188 | res.status(result.status).send( FAILURE(result.message) )
189 | } else{
190 | res.send( SUCCESS(result.problem) )
191 | }
192 | },
193 |
194 | update_problem: async(req, res) => {
195 | const result = await update_problem(req.query, req.body);
196 | if(result.failed) {
197 | res.status(result.status).send( FAILURE(result.message) )
198 | } else{
199 | res.send( SUCCESS(result.problem) )
200 | }
201 | },
202 |
203 | get_problem: async(req, res) => {
204 | const result = await get_problem(req.query, req.locals);
205 | if(result.failed) {
206 | res.status(result.status).send( FAILURE(result.message) )
207 | } else{
208 | res.send( SUCCESS(result.problem) )
209 | }
210 | },
211 |
212 | search_problem: async(req, res) => {
213 | const result = await search_problem(req.query, req.locals);
214 | if(result.failed) {
215 | res.status(result.status).send( FAILURE(result.message) )
216 | } else{
217 | res.send( SUCCESS(result.problems) )
218 | }
219 | },
220 |
221 | delete_problem: async (req, res) => {
222 | const result = await delete_problem(req.query);
223 | if(result.failed) {
224 | res.status(result.status).send( FAILURE(result.message) )
225 | } else{
226 | res.send( SUCCESS(result.problem) )
227 | }
228 | }
229 | }
230 |
--------------------------------------------------------------------------------
/Frontend/src/page/Status.js:
--------------------------------------------------------------------------------
1 |
2 | import React, { useEffect, useState } from 'react';
3 | import { Link, useHistory, useLocation } from 'react-router-dom';
4 | import ReactLoading from 'react-loading';
5 | import queryString from 'query-string'
6 |
7 | import {BACKEND_URL, PAGINATION} from '../util/config'
8 |
9 | function Status() {
10 |
11 | const [submissionData, setSubmissionData] = useState(null)
12 | const query = queryString.parse(useLocation().search)
13 | const [page, setPage] = useState( Math.max(query.page || 1), 1 )
14 | const [username, setUsername] = useState(query.username)
15 | const [problem_id, setProblem_id] = useState(query.problem_id)
16 | const [verdict, setVerdict] = useState(query.verdict)
17 | const history = useHistory()
18 |
19 | const handleEnter = (event) => {
20 | if(event.key === 'Enter') goto()
21 | }
22 |
23 |
24 | useEffect(() => {
25 |
26 | try{
27 | (async() => {
28 | const query_object = {}
29 | if(username) query_object.username = username
30 | if(page) query_object.page = page
31 | if(problem_id) query_object.problem_id = problem_id
32 | if(verdict) query_object.verdict = verdict
33 | const response = await fetch( `${BACKEND_URL}/submission/status?${queryString.stringify(query_object)}`, {
34 | credentials: 'include',
35 | headers: {
36 | 'Content-Type': 'application/json'
37 | },
38 | })
39 | const data = await response.json();
40 | //console.log(data)
41 | if(data.success === "yes"){
42 | data.data.forEach(item => {
43 | item.problem_title = item.Problem.title
44 | })
45 | setSubmissionData(data.data)
46 | } else{
47 | // history.push('/404')
48 | // history.go(0)
49 | }
50 |
51 | })()
52 | } catch(err){
53 | // history.push('/404')
54 | // history.go(0)
55 | // console.log(err)
56 | }
57 |
58 | }, [])
59 |
60 | const verdictStyle = (verdict)=> {
61 | if(verdict === "Accepted") return " text-green-600"
62 | else if(verdict === "Not Judged Yet") return ""
63 | else return " text-red-600"
64 | }
65 |
66 | const goto = () => {
67 | const query_object = {}
68 | if(username) query_object.username = username
69 | if(page) query_object.page = page
70 | if(problem_id) query_object.problem_id = problem_id
71 | if(verdict) query_object.verdict = verdict
72 | history.push(`/status?${queryString.stringify(query_object)}`)
73 | history.go(0)
74 | }
75 |
76 | return (
77 |
78 | {!submissionData &&
79 |
80 |
81 |
82 | }
83 | {submissionData &&
84 |
85 |
86 |
GlaxOJ Judge Status
87 |
88 |
89 |
90 |
91 |
92 | Page
93 | setPage( Math.max( parseInt(e.target.value.trim()), 1 ) )} onKeyPress={handleEnter}/>
94 | ↣
95 |
96 |
97 |
98 |
142 |
}
143 |
144 | );
145 | }
146 |
147 | export default Status;
148 |
--------------------------------------------------------------------------------
/Frontend/src/page/Problem.js:
--------------------------------------------------------------------------------
1 |
2 | import React, { useContext, useEffect, useState } from 'react';
3 | import queryString from 'query-string'
4 | import ReactLoading from 'react-loading';
5 |
6 | import {BACKEND_URL, PAGINATION} from '../util/config'
7 | import { Link, useHistory, useLocation } from 'react-router-dom';
8 | import { UserContext } from '../util/UserContext';
9 |
10 | function Problem() {
11 |
12 | const query = queryString.parse(useLocation().search)
13 | const history = useHistory()
14 |
15 | const [problem, setProblem] = useState(null)
16 | const [page, setPage] = useState( Math.max(query.page || 1), 1 )
17 | const tag = query.tag
18 | const [title, setTitle] = useState(query.title)
19 | const author = query.author
20 | const admin = query.admin
21 |
22 | const [userFromContext, setUserFromContext] = useContext(UserContext)
23 |
24 | // console.log(page, tag, title, admin)
25 | // console.log(queryString.stringify(query))
26 |
27 | useEffect(() => {
28 |
29 | try{ //console.log('query', queryString.stringify(query))
30 | (async()=> {
31 |
32 |
33 | const query_object = {}
34 | if(page) query_object.page = page
35 | if(title) query_object.title = title
36 | if(admin) query_object.admin = admin
37 | if(tag) query_object.tag = tag
38 | if(author) query_object.author = author
39 |
40 | // console.log(page, tag, title, admin)
41 | //console.log(`${BACKEND_URL}/problem/search?${queryString.stringify(query_object)}`)
42 |
43 | const response = await fetch( `${BACKEND_URL}/problem/search?${queryString.stringify(query_object)}`, {
44 | credentials: 'include',
45 | headers: {
46 | 'Content-Type': 'application/json'
47 | },
48 | })
49 | const data = await response.json()
50 | //console.log(data)
51 | if(data.success === "yes") {
52 | data.data.forEach(problem => {
53 | problem.tag = problem.tag.map(item => {
54 | return ( {item})
55 | })
56 | })
57 | setProblem(data.data)
58 | } else {
59 | history.push('/404')
60 | // history.go(0)
61 | }
62 |
63 | })()
64 | }catch(err){
65 | //console.log(err)
66 |
67 | }
68 |
69 |
70 | }, [])
71 |
72 |
73 | const goto = () => {
74 | const query_object = {}
75 | if(page) query_object.page = page
76 | if(title) query_object.title = title
77 | if(admin) query_object.admin = admin
78 | if(tag) query_object.tag = tag
79 | history.push(`/problem?${queryString.stringify(query_object)}`)
80 | history.go(0)
81 | }
82 |
83 | // const gotoEdit = (problem_id) => {
84 | // console.log(problem_id)
85 | // }
86 |
87 | const handleEnter = (event) => {
88 | if(event.key === 'Enter') goto()
89 | }
90 |
91 | const putStatusIcon = (problem_id) => {
92 |
93 | //console.log(userFromContext, userFromContext.solved_problem.length)
94 |
95 | for(let i = 0; i < userFromContext.solved_problem.length; i++){
96 | if(userFromContext.solved_problem[i].id === problem_id) {
97 | return '✔'
98 | }
99 | }
100 |
101 | for(let i = 0; i < userFromContext.unsolved_problem.length; i++){
102 | if(userFromContext.unsolved_problem[i].id === problem_id) {
103 | return '❌'
104 | }
105 | }
106 |
107 | return '-'
108 |
109 | }
110 |
111 |
112 | return (
113 |
114 | {!problem &&
115 |
116 |
117 |
118 | }
119 | {problem &&
120 |
125 |
126 |
127 |
128 | Page
129 | setPage( Math.max( parseInt(e.target.value.trim()), 1 ) )} onKeyPress={handleEnter}/>
130 | ↣
131 |
132 |
133 |
134 |
168 |
169 | }
170 |
171 | );
172 | }
173 |
174 | export default Problem;
175 |
--------------------------------------------------------------------------------
/Frontend/src/page/User.js:
--------------------------------------------------------------------------------
1 |
2 | import React, { useContext, useEffect, useState } from 'react';
3 |
4 | import {BACKEND_URL} from '../util/config'
5 | import ReactLoading from 'react-loading';
6 | import { Link, useHistory } from 'react-router-dom';
7 |
8 | import {UserContext} from '../util/UserContext'
9 |
10 | function User(props) {
11 | const username = props.match.params.username
12 | // console.log(username)
13 | const [user, setUser] = useState(null)
14 | const history = useHistory()
15 |
16 | const [userFromContext, setUserFromContext] = useContext(UserContext)
17 |
18 | //console.log(userFromContext)
19 |
20 |
21 | useEffect( () => {
22 |
23 | // history.go(0)
24 |
25 | (async() => {
26 |
27 | try{
28 |
29 | setUser(null)
30 |
31 | const response = await fetch( `${BACKEND_URL}/user?username=${username}`, {
32 | credentials: 'include',
33 | headers: {
34 | 'Content-Type': 'application/json'
35 | },
36 | })
37 | const user_data = await response.json()
38 | if(user_data.success === "yes") setUser(user_data.data)
39 | else {
40 | history.push('/404')
41 | // history.go(0)
42 | }
43 | } catch(err){
44 | // console.log(err)
45 | history.push('/404')
46 | // history.go(0)
47 | }
48 | })()
49 |
50 |
51 | }, [username])
52 |
53 | const changeAdmin = async()=>{
54 | try {
55 | let admin = !(user.role && user.role.indexOf('ADMIN') !== -1)
56 | //console.log(`${BACKEND_URL}/user?username=${username}&admin=${admin}`)
57 | const response = await fetch( `${BACKEND_URL}/user?username=${username}&admin=${admin}`, {
58 | credentials: 'include',
59 | method: 'PUT',
60 | headers: {
61 | 'Content-Type': 'application/json'
62 | },
63 | })
64 | const user_data = await response.json()
65 | // setUser(user_data.data)
66 | if(user_data.success === "yes") {
67 | history.go(0)
68 | }
69 |
70 | // console.log(user_data)
71 |
72 | } catch(err){
73 | // console.log(err)
74 | }
75 | }
76 |
77 | const changeDisable = async()=>{
78 | try {
79 | let disable = !(user.disable)
80 | const response = await fetch( `${BACKEND_URL}/user?username=${username}&disable=${disable}`, {
81 | credentials: 'include',
82 | method: 'PUT',
83 | headers: {
84 | 'Content-Type': 'application/json'
85 | },
86 | })
87 | const user_data = await response.json()
88 | // console.log(user_data)
89 | // setUser(user_data.data)
90 |
91 | if(user_data.success === "yes") {
92 | history.go(0)
93 | }
94 |
95 | } catch(err){
96 | // console.log(err)
97 | }
98 | }
99 |
100 | const logout = async ()=> {
101 | try {
102 | const response = await fetch( `${BACKEND_URL}/user/logout`, {
103 | credentials: 'include',
104 | headers: {
105 | 'Content-Type': 'application/json'
106 | },
107 | })
108 | const user_data = await response.json()
109 |
110 | if(user_data.success === "yes") {
111 | setUserFromContext(null)
112 | history.push('/')
113 | // history.go(0)
114 | }
115 |
116 | } catch(err){
117 | // console.log(err)
118 | }
119 | }
120 |
121 |
122 | return (
123 |
124 |
125 | { !user &&
126 |
127 |
128 | }
129 | {user &&
130 |
131 |
134 |
135 |
136 |
137 |
{user.username}
138 |
139 |
140 |
{user.role?.join(', ')}
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 | {/*
Submissions */}
149 |
150 |
151 |
152 | Name
153 | {user.fullname}
154 |
155 |
156 |
157 | {user.email &&
158 | Email
159 | {user.email}
160 | }
161 |
162 |
163 | Solved
164 | {user.solved_problem.length}
165 |
166 |
167 |
168 |
169 | Submissions
170 | {user.total_submission}
171 |
172 |
173 | Account Created At
174 | {new Date(user.createdAt).toLocaleString()}
175 |
176 |
177 |
178 |
179 |
180 | View Submissions
181 | {userFromContext && userFromContext.username === user.username && Logout }
182 |
183 |
184 |
185 |
186 |
187 | Solved Problem(s)
188 |
189 |
190 | {user.solved_problem.map((each, index) => {
191 | return (
192 |
193 | {each.id} {index===user.solved_problem.length-1?"":", "}
194 |
195 | )
196 | })}
197 |
198 |
199 |
200 |
201 | Tried Problem(s)
202 |
203 | {user.unsolved_problem.map((each, index) => {
204 | return (
205 |
206 | {each.id} {index===user.unsolved_problem.length-1?"":", "}
207 |
208 | )
209 | })}
210 |
211 |
212 |
213 |
214 | { userFromContext && userFromContext.role && userFromContext.role.indexOf('ADMIN') !== -1 &&
215 | {(user.role && user.role.indexOf('ADMIN') !== -1)? "Remove Admin" : "Make Admin"}
216 | {!user.disable ? "Disable" : "Enable"}
217 |
218 | }
219 |
}
220 |
221 |
222 |
223 | );
224 | }
225 |
226 | export default User;
227 |
--------------------------------------------------------------------------------
/controller/submission/submission.controller.js:
--------------------------------------------------------------------------------
1 | const {Submission, Problem} = require('../../models')
2 | const {SUCCESS, FAILURE, STATUS} = require('../../api_response')
3 | const sequelize = require('sequelize')
4 | const judge = require('../judge')
5 |
6 | // const verdicts = {
7 | // accepted: "Accepted",
8 | // not_judged_yet: "Not Judged Yet",
9 | // unknown_error: "Unknown Error",
10 | // time_limit_exceeded: "Time Limit Exceeded",
11 | // memory_limit_exceeded: "Memory Limit Exceeded",
12 | // compilation_error: "Compilation Error",
13 | // runtime_error: "Runtime Error",
14 | // judging: "Judging",
15 | // wrong_answer: "Wrong Answer"
16 | // }
17 |
18 | const submit = async(data, user) => {
19 | try{
20 |
21 | if(user.disable) {
22 | throw new Error('You are currently blocked from the server.')
23 | }
24 |
25 | if(!judge.judge_state()) {
26 | throw new Error('Judge is busy. Please try again later.')
27 | }
28 |
29 | if(judge.size() >= parseInt(process.env.JUDGE_QUEUE_CAPACITY)) { // for current security
30 | throw new Error('Judge is busy. Please try again later.')
31 | }
32 |
33 | data.username = user.username
34 |
35 |
36 | const submission = await Submission.create(data)
37 | // const problem = await Problem.findOne({
38 | // where: {
39 | // id: submission.problem_id,
40 | // disable: false
41 | // },
42 | // attributes: ['id', 'inputs', 'outputs', 'time_limit']
43 | // })
44 |
45 | // if(!problem) throw new Error("Problem not found")
46 |
47 |
48 | /**
49 | *
50 | * bunch of asynchronus code
51 | *
52 | * fetches problem
53 | * sends to judge
54 | * gets verdict through a callback
55 | */
56 | Problem.findOne({
57 | where: {
58 | id: submission.problem_id,
59 | disable: false
60 | },
61 | attributes: ['id', 'inputs', 'outputs', 'time_limit']
62 | }).then( problem => {
63 | if(problem){
64 | try{
65 | judge.add_to_queue({ // judgement day
66 | submission_id: submission.id,
67 | source_code: submission.source_code,
68 | language: submission.language,
69 | inputs: problem.inputs,
70 | outputs: problem.outputs,
71 | time_limit: problem.time_limit
72 | }, verdict => {
73 | // console.log(verdict)
74 | submission.verdict = verdict;
75 | submission.save()
76 | })
77 | } catch(err) {
78 | submission.verdict = "Runtime Error";
79 | submission.save()
80 | }
81 | } else {
82 | submission.destroy()
83 | }
84 |
85 | } ).catch(err => {
86 | console.log(err)
87 | })
88 |
89 | return {submission}
90 |
91 | } catch(err) {
92 | return {
93 | status: STATUS.BAD_REQUEST,
94 | message: err.message,
95 | failed: true
96 | }
97 | }
98 | }
99 |
100 | const get_submission = async(data, user) => {
101 | try{
102 | const submission = await Submission.findOne({
103 | where: {
104 | id : data.id,
105 | },
106 | include: [{
107 | model: Problem,
108 | attributes: ['title'],
109 | where: {
110 | disable: false
111 | }
112 | }],
113 | raw: true
114 | })
115 |
116 | if(!submission) throw new Error('Not found')
117 |
118 | if(!submission["Problem.title"]) throw new Error('Not found')
119 |
120 | if(submission.username !== user.username && !await Submission.findOne({where: {
121 | username: user.username,
122 | verdict: 'Accepted'
123 | }})) submission.source_code = `
124 |
125 | /*******************************************************/
126 | /*-----------------------------------------------------*/
127 | /*------------Please solve the problem first.----------*/
128 | /*---------------------Best of luck!-------------------*/
129 | /*-----------------------------------GlaxOJ------------*/
130 | /*-----------------------------------------------------*/
131 | /*******************************************************/`
132 |
133 | return {submission}
134 |
135 | } catch(err) {
136 | return {
137 | status: STATUS.BAD_REQUEST,
138 | message: err.message,
139 | failed: true
140 | }
141 | }
142 | }
143 |
144 | const status = async (query) => {
145 | try{
146 |
147 | let search_object = {}
148 |
149 | if(query.username) {
150 | search_object = {
151 | ...search_object,
152 | username: query.username
153 | }
154 | }
155 |
156 | if(query.problem_id) {
157 | search_object = {
158 | ...search_object,
159 | problem_id: query.problem_id
160 | }
161 | }
162 |
163 | if(query.verdict) {
164 | search_object = {
165 | ...search_object,
166 | verdict: query.verdict
167 | }
168 | }
169 |
170 | const page = parseInt(query.page) || 1;
171 | const limit = parseInt( process.env.PAGINATION )
172 | const offset = limit * (page - 1)
173 |
174 | const submissions = await Submission.findAll({
175 | where: search_object,
176 | attributes: ['id', 'username', 'problem_id', 'verdict', 'language', 'createdAt', 'updatedAt'],
177 | limit,
178 | offset,
179 | order: sequelize.literal('"createdAt" DESC'),
180 | include: [{
181 | model: Problem,
182 | attributes: ['title'],
183 | }]
184 | });
185 | return {submissions}
186 |
187 | } catch(err) { //console.log(err)
188 | return {
189 | status: STATUS.BAD_REQUEST,
190 | message: err.message,
191 | failed: true
192 | }
193 | }
194 | }
195 |
196 | const rank = async (query) => {
197 | try{
198 | const page = parseInt(query.page) | 1;
199 | const limit = parseInt( process.env.PAGINATION )
200 | const offset = limit * (page - 1)
201 |
202 | const where_query = { verdict: 'Accepted' };
203 | if(query.username) where_query.username = query.username
204 |
205 | const rank = await Submission.findAll({
206 | where: where_query,
207 | attributes: ['username', [sequelize.fn('count', sequelize.literal('DISTINCT(problem_id)')), 'solve']], //(DISTINCT(problem_id))
208 | group: ['Submission.username'],
209 | order: sequelize.literal('solve DESC'),
210 | limit,
211 | offset,
212 | });
213 |
214 | return {rank}
215 | } catch(err) {
216 | return {
217 | status: STATUS.BAD_REQUEST,
218 | message: err.message,
219 | failed: true
220 | }
221 | }
222 | }
223 |
224 | const count = async (query) => {
225 | try{
226 |
227 | let search_object = {}
228 |
229 | if(query.username) {
230 | search_object = {
231 | ...search_object,
232 | username: query.username
233 | }
234 | }
235 |
236 | if(query.problem_id) {
237 | search_object = {
238 | ...search_object,
239 | problem_id: query.problem_id
240 | }
241 | }
242 |
243 | if(query.verdict) {
244 | search_object = {
245 | ...search_object,
246 | verdict: query.verdict
247 | }
248 | }
249 |
250 | const count = await Submission.count({
251 | where: search_object
252 | });
253 | return {count}
254 |
255 | } catch(err) {
256 | return {
257 | status: STATUS.BAD_REQUEST,
258 | message: err.message,
259 | failed: true
260 | }
261 | }
262 | }
263 |
264 | const get_ac = async(query) => {
265 | try{
266 | if(!query.username) throw new Error('not found'
267 | )
268 | // const ac = await Submission.findAll({
269 | // where: {
270 | // verdict: 'Accepted'
271 | // },
272 | // attributes: [[sequelize.fn('DISTINCT', sequelize.col('"problem_id"')) ,'id']],
273 | // include: [{
274 | // model: Problem,
275 | // attributes: ['title']
276 | // }]
277 | // });
278 |
279 | const ac = await Submission.aggregate('problem_id', 'DISTINCT', {
280 | plain: false,
281 | include: [{
282 | model: Problem,
283 | attributes: ['title']
284 | }]
285 | })
286 | ac.forEach(each => {
287 | // each.id = each.Problem.id
288 | // each.title = each.Problem.title
289 | // delete each.Problem.id
290 | // delete each.Problem.title
291 | // delete each.DISTINCT
292 | })
293 | return {ac}
294 |
295 | } catch(err) {
296 | return {
297 | status: STATUS.BAD_REQUEST,
298 | message: err.message,
299 | failed: true
300 | }
301 | }
302 | }
303 |
304 | const delete_submission = async(query) => { //console.log('came')
305 | try{
306 | if(!query.id) throw new Error('not found')
307 |
308 | const submission = await Submission.destroy({
309 | where: {
310 | id: query.id
311 | }
312 | })
313 | // console.log(submission)
314 | if(!submission) throw new Error("not found")
315 | return {submission}
316 |
317 | } catch(err) {
318 | return {
319 | status: STATUS.BAD_REQUEST,
320 | message: err.message,
321 | failed: true
322 | }
323 | }
324 | }
325 |
326 |
327 | module.exports = {
328 |
329 | submit: async(req, res) => {
330 | const result = await submit(req.body, req.locals); // gotta change this
331 | if(result.failed) {
332 | res.status(result.status).send( FAILURE(result.message) )
333 | } else{
334 | res.send( SUCCESS(result.submission) )
335 | }
336 | },
337 |
338 | get_submission: async(req, res) => {
339 | const result = await get_submission(req.query, req.locals); // change username
340 | if(result.failed) {
341 | res.status(result.status).send( FAILURE(result.message) )
342 | } else{
343 | res.send( SUCCESS(result.submission) )
344 | }
345 | },
346 |
347 | status: async(req, res) => {
348 | const result = await status(req.query); // change username
349 | if(result.failed) {
350 | res.status(result.status).send( FAILURE(result.message) )
351 | } else{
352 | res.send( SUCCESS(result.submissions) )
353 | }
354 | },
355 |
356 | rank: async(req, res) => {
357 | const result = await rank(req.query); // change username
358 | if(result.failed) {
359 | res.status(result.status).send( FAILURE(result.message) )
360 | } else{
361 | res.send( SUCCESS(result.rank) )
362 | }
363 | },
364 |
365 | count: async(req, res) => {
366 | const result = await count(req.query); // change username
367 | if(result.failed) {
368 | res.status(result.status).send( FAILURE(result.message) )
369 | } else{
370 | res.send( SUCCESS(result.count) )
371 | }
372 | },
373 |
374 | get_judge_status: async (req, res) => {
375 | res.send( SUCCESS(judge.judge_state()) )
376 | },
377 |
378 | toggle_judge: async(req, res) => {
379 | if(judge.judge_state()) {
380 | judge.stop_judge_queue()
381 | } else {
382 | judge.start_judge_queue()
383 | }
384 | res.send( SUCCESS(judge.judge_state()) )
385 | },
386 |
387 | get_ac: async(req, res) => {
388 | const result = await get_ac(req.query); // change username
389 | if(result.failed) {
390 | res.status(result.status).send( FAILURE(result.message) )
391 | } else{
392 | res.send( SUCCESS(result.ac) )
393 | }
394 | },
395 |
396 | delete_submission: async(req, res) => { //console.log('came', req.query)
397 | const result = await delete_submission(req.query);
398 | // console.log()
399 | if(result.failed) {
400 | res.status(result.status).send( FAILURE(result.message) )
401 | } else{
402 | res.send( SUCCESS(result.submission) )
403 | }
404 | },
405 |
406 | ektu_chalak_function: () => { //console.log('called')
407 | Submission.update({ verdict: "Runtime Error" }, {
408 | where: {
409 | verdict: "Not Judged Yet"
410 | }
411 | });
412 |
413 | }
414 | }
415 |
--------------------------------------------------------------------------------
/Frontend/src/page/EditProblem.js:
--------------------------------------------------------------------------------
1 |
2 | import React, { useEffect, useState } from 'react';
3 |
4 | import {Link, useHistory, useLocation } from 'react-router-dom';
5 | import ReactLoading from 'react-loading';
6 | import queryString from 'query-string'
7 |
8 | // import EditorJs from 'react-editor-js';
9 | // import RichTextEditor from 'react-rte';
10 |
11 | // import { EDITOR_JS_TOOLS } from '../util/tool'
12 | // console.log(EDITOR_JS_TOOLS)
13 | // import Header from '@editorjs/header'
14 | import {BACKEND_URL} from '../util/config'
15 |
16 | /**
17 | *
18 | * id
19 | * memory_limit
20 | * time_limit
21 | * content
22 | * inputs
23 | * outputs
24 | * title
25 | * tag
26 | * author
27 | */
28 |
29 | function EditProblem() {
30 |
31 | const query = queryString.parse(useLocation().search)
32 | const history = useHistory()
33 | const problem_id = query.problem_id
34 | const [memoryLimit, setMemoryLimit] = useState(32)
35 | const [timeLimit, setTimeLimit] = useState(1)
36 | const [content, setContent] = useState( null )
37 | const [inputs, setInputs] = useState([])
38 | const [outputs, setOutputs] = useState([])
39 | const [title, setTitle] = useState("")
40 | const [tag, setTag] = useState("")
41 | const [author, setAuthor] = useState("")
42 | const [disable, setDisable] = useState(false)
43 | // const [sampleInput, setSampleInput] = useState("")
44 | // const [sampleOutput, setSampleOutput] = useState("")
45 | const [loading, setLoading] = useState(true)
46 | // const [testCaseFile, setTestCaseFile] = useState([])
47 | const [noOfTestCase, setNoOfTestCase] = useState(0)
48 | const [error, setError] = useState("")
49 |
50 |
51 | const temporary_inputs = [...Array(20)].map(x => null)
52 | const temporary_outputs = [...Array(20)].map(x => null)
53 |
54 | for(let i = 0; i < noOfTestCase; i++) {
55 | temporary_inputs[i] = inputs[i]
56 | temporary_outputs[i] = outputs[i]
57 | }
58 |
59 |
60 | useEffect(() => {
61 |
62 | try{
63 |
64 | (async() => {
65 |
66 | const response = await fetch( `${BACKEND_URL}/user/is_admin`, {
67 | credentials: 'include',
68 | headers: {
69 | 'Content-Type': 'application/json'
70 | },
71 | })
72 | const data = await response.json()
73 | if(data.success === "yes") {
74 | if(problem_id) {
75 | const response2 = await fetch( `${BACKEND_URL}/problem?id=${problem_id}&admin=true`, {
76 | credentials: 'include',
77 | headers: {
78 | 'Content-Type': 'application/json'
79 | },
80 | })
81 | const data2 = await response2.json()
82 | if(data2.success === "yes") {
83 | const problem = data2.data
84 | //console.log(problem)
85 | setAuthor(problem.author)
86 | setTitle(problem.title)
87 | setTimeLimit(problem.time_limit)
88 | setMemoryLimit(problem.memory_limit)
89 | setTag(problem.tag.join(', '))
90 | setDisable(problem.disable)
91 | setNoOfTestCase(problem.inputs.length)
92 | setContent(problem.content)
93 | setInputs(problem.inputs)
94 | setOutputs(problem.outputs)
95 |
96 | // setDescription(content.description)
97 | // setInputSpecification(content.inputSpecification)
98 | // setOutputSpecification(content.outputSpecification)
99 | // setSampleTestCase(content.sampleTestCase)
100 | // setNote(content.note)
101 |
102 |
103 | setLoading(false)
104 | } else {
105 | history.push('/404')
106 | history.go(0)
107 | }
108 | }
109 | setLoading(false)
110 | } else{
111 | history.push('/404')
112 | // history.go(0)
113 | }
114 |
115 | })()
116 |
117 | }catch(err){
118 | // console.log(err)
119 | setError(err.message)
120 | }
121 |
122 |
123 | }, [])
124 |
125 |
126 | const submit = async () => {
127 | // set inputs / outputs first
128 | // console.log(temporary_inputs, temporary_outputs)
129 | // setInputs(temporary_inputs.splice(0, noOfTestCase))
130 | // setOutputs(temporary_outputs.splice(0, noOfTestCase))
131 | // console.log(inputs, outputs)
132 | // return;
133 | const post_data = {}
134 | /**
135 | *
136 | * id
137 | * memory_limit
138 | * time_limit
139 | * content
140 | * inputs
141 | * outputs
142 | * title
143 | * tag
144 | * author
145 | */
146 | //console.log(inputs, outputs)
147 | if(inputs.length !== outputs.length || inputs.length == 0 || outputs.length == 0) { //console.log('1')
148 | setError('Set inputs and outputs carefully')
149 | return;
150 | }
151 | for(let index = 0; index < inputs.length; index++) {
152 | if(!outputs[index] || !inputs[index]) {//console.log('1')
153 | setError('Set inputs and outputs carefully')
154 | return;
155 | }
156 | }
157 | post_data.memory_limit = memoryLimit
158 | post_data.time_limit = timeLimit
159 | post_data.content = content
160 | post_data.title = title
161 | post_data.tag = tag.split(',')
162 | post_data.author = author
163 | post_data.disable = disable
164 | post_data.inputs = inputs
165 | post_data.outputs = outputs
166 | //console.log(post_data)
167 |
168 | if(problem_id) { // it is an edit request
169 | const response = await fetch( `${BACKEND_URL}/problem?id=${problem_id}`, {
170 | credentials: 'include',
171 | method: 'PUT',
172 | headers: {
173 | 'Content-Type': 'application/json'
174 | },
175 | body: JSON.stringify(post_data)
176 | })
177 | const response_data = await response.json()
178 | if(response_data.success === "yes") {
179 | setError('')
180 | history.push('/problem?admin=true')
181 | history.go(0)
182 | } else {
183 | setError(response_data.message)
184 | }
185 |
186 | } else { // create request
187 | const response = await fetch( `${BACKEND_URL}/problem/`, {
188 | credentials: 'include',
189 | method: 'POST',
190 | headers: {
191 | 'Content-Type': 'application/json'
192 | },
193 | body: JSON.stringify(post_data)
194 | })
195 | const response_data = await response.json()
196 | if(response_data.success === "yes") {
197 | setError('')
198 | history.push('/problem?admin=true')
199 | //history.go(0)
200 | } else {
201 | setError(response_data.message)
202 | }
203 | }
204 |
205 | }
206 |
207 |
208 |
209 | const setOutputEach = (index, value) => {
210 | // console.log(index, value)
211 | // let temp = outputs;
212 | // if(temp.length !== noOfTestCase) temp = [...Array(noOfTestCase)].map(x => null);
213 | // temp[index] = value
214 | // setOutputs(temp)
215 | temporary_outputs[index] = value
216 | const next_temp = []
217 | for(let i = 0; i < noOfTestCase; i++) next_temp.push(temporary_outputs[i])
218 | setOutputs(next_temp)
219 | }
220 |
221 | const setInputEach = (index, value) => {
222 | // console.log(index, value)
223 | // if(inputs.length !== noOfTestCase) setInputs([...Array(noOfTestCase)].map(x => null))
224 | // const temp = inputs
225 | // temp[index] = value
226 | // setInputs(temp)
227 | temporary_inputs[index] = value
228 | const next_temp = []
229 | for(let i = 0; i < noOfTestCase; i++) next_temp.push(temporary_inputs[i])
230 | setInputs(next_temp)
231 | }
232 |
233 | const deleteProblem = async()=> {
234 | const response = await fetch( `${BACKEND_URL}/problem?id=${problem_id}`, {
235 | credentials: 'include',
236 | method: 'DELETE',
237 | headers: {
238 | 'Content-Type': 'application/json'
239 | },
240 | })
241 | const response_data = await response.json()
242 | if(response_data.success === "yes") {
243 | setError('')
244 | history.push('/problem?admin=true')
245 | //history.go(0)
246 | } else {
247 | setError(response_data.message)
248 | }
249 | }
250 |
251 | const handleResetTestCases = () => {
252 | setNoOfTestCase(0)
253 | setInputs([])
254 | setOutputs([])
255 | }
256 |
257 | return (
258 |
259 | {loading &&
260 |
261 |
262 | }
263 | {!loading &&
264 |
265 |
268 |
269 |
270 |
335 |
336 |
337 |
338 |
339 | Test No
340 | Input
341 | Output
342 |
343 |
344 | {
345 | [...Array(noOfTestCase)].map((tc, index) => {
346 | return (
347 |
348 | {index + 1}
349 |
350 |
351 |
352 | )
353 | })
354 | }
355 |
356 |
357 |
358 |
359 | {error &&
}
362 |
363 |
364 | {/* Preview */}
365 | handleResetTestCases()}>Clear Test Cases
366 | Save
367 | Delete
368 |
369 |
370 |
371 |
372 |
373 |
374 | }
375 |
376 |
377 | );
378 | }
379 |
380 | export default EditProblem;
381 |
--------------------------------------------------------------------------------