├── .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 | 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 |
16 |
But we are.
17 |
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 | Logo 28 |
29 |
30 | Your friendly online judge 31 |
32 |
33 |
34 | 35 |
36 |
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 |
67 |
68 | 69 |
70 |
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 | ![GlaxOJ Landing Page](https://i.imgur.com/zii0mHk.png) 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 | 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 |
} 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 |
64 |
Rank
65 |
66 |
67 |
68 | 69 | setPage( Math.max( parseInt(e.target.value.trim()), 1 ) )} onKeyPress={handleEnter}/> 70 | 71 |
72 |
73 |
74 |
75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | { rank.map((individual, index) => { 85 | return ( 86 | 87 | 88 | 89 | ) 90 | }) } 91 | 92 |
RankUsernameSolve
{index + 1 + ((query.page || 1) - 1) * PAGINATION}{individual.username}{individual.solve}
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 | 69 |
70 |
71 |
72 | {/*