├── Procfile ├── .gitignore ├── Images ├── Blogs.PNG ├── contest.PNG ├── homepage.PNG ├── profile.PNG └── championship.PNG ├── gif └── codencollab.gif ├── src ├── db │ └── mongoose.js ├── Function │ ├── getKey.js │ ├── Oauth2.js │ └── compilerFunc.js ├── models │ ├── Reply.js │ ├── Comment.js │ ├── Blogs.js │ └── User.js ├── middleware │ └── Auth.js ├── socketFunc │ ├── chat.js │ ├── Compile.js │ ├── problem.js │ ├── Contest-Join.js │ └── userJoin.js ├── index.js ├── routers │ ├── OauthRouter.js │ ├── UserRouter.js │ ├── ReplyRouter.js │ ├── BlogRouter.js │ └── CommentRouter.js └── utils │ ├── Users.js │ └── Contest.js ├── package.json ├── .github ├── workflows │ └── node.js.yml └── pull_request_template.md ├── CONTRIBUTING.md ├── CODE_OF_CONDUCT.md ├── README.md └── LICENSE /Procfile: -------------------------------------------------------------------------------- 1 | web: node src/index.js 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | Configs 3 | nodemon.json 4 | .env -------------------------------------------------------------------------------- /Images/Blogs.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atharmohammad/Code-N-Collab-Server/HEAD/Images/Blogs.PNG -------------------------------------------------------------------------------- /Images/contest.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atharmohammad/Code-N-Collab-Server/HEAD/Images/contest.PNG -------------------------------------------------------------------------------- /Images/homepage.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atharmohammad/Code-N-Collab-Server/HEAD/Images/homepage.PNG -------------------------------------------------------------------------------- /Images/profile.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atharmohammad/Code-N-Collab-Server/HEAD/Images/profile.PNG -------------------------------------------------------------------------------- /gif/codencollab.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atharmohammad/Code-N-Collab-Server/HEAD/gif/codencollab.gif -------------------------------------------------------------------------------- /Images/championship.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atharmohammad/Code-N-Collab-Server/HEAD/Images/championship.PNG -------------------------------------------------------------------------------- /src/db/mongoose.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | mongoose.connect( 3 | process.env.MONGO_DB_URL, 4 | { useNewUrlParser: true, useCreateIndex: true }, 5 | (e, r) => { 6 | if (e) { 7 | return console.log("error", e); 8 | } 9 | console.log("connected"); 10 | } 11 | ); 12 | -------------------------------------------------------------------------------- /src/Function/getKey.js: -------------------------------------------------------------------------------- 1 | let idx = -1; 2 | 3 | const keys = [ 4 | [process.env.COMPILE_CLIENT_ID1, process.env.COMPILE_CLIENT_SECRET1], 5 | [process.env.COMPILE_CLIENT_ID2, process.env.COMPILE_CLIENT_SECRET2], 6 | [process.env.COMPILE_CLIENT_ID3, process.env.COMPILE_CLIENT_SECRET3], 7 | [process.env.COMPILE_CLIENT_ID4, process.env.COMPILE_CLIENT_SECRET4], 8 | ]; 9 | 10 | const sz = keys.length; 11 | 12 | const AwesomeKey = () => { 13 | idx++; 14 | if (idx == sz) idx = 0; 15 | 16 | return keys[idx]; 17 | }; 18 | 19 | module.exports = { 20 | AwesomeKey, 21 | }; 22 | -------------------------------------------------------------------------------- /src/models/Reply.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const replySchema = new mongoose.Schema( 4 | { 5 | Body: { 6 | type: String, 7 | }, 8 | User: { 9 | type: mongoose.Schema.Types.ObjectId, 10 | required: true, 11 | ref: "User", 12 | }, 13 | Comment: { 14 | type: mongoose.Schema.Types.ObjectId, 15 | required: true, 16 | ref: "Comment", 17 | }, 18 | Likes: [ 19 | { 20 | type: mongoose.Schema.Types.ObjectId, 21 | ref: "User", 22 | required: true, 23 | }, 24 | ], 25 | Deleted: { 26 | type: Boolean, 27 | default: false, 28 | }, 29 | }, 30 | { 31 | timestamps: true, 32 | } 33 | ); 34 | 35 | const table = mongoose.model("Reply", replySchema); 36 | 37 | module.exports = table; 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "codencollab-server", 3 | "version": "1.0.0", 4 | "description": "Code-n-Collab Server", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node src/index.js", 8 | "dev": "nodemon src/index.js" 9 | }, 10 | "author": "Mohd Athar / Adnan Shamsi", 11 | "license": "ISC", 12 | "dependencies": { 13 | "axios": "^0.21.1", 14 | "bcryptjs": "^2.4.3", 15 | "cors": "^2.8.5", 16 | "dotenv": "^10.0.0", 17 | "express": "^4.17.1", 18 | "googleapis": "^74.2.0", 19 | "jsonwebtoken": "^8.5.1", 20 | "mongoose": "^5.12.12", 21 | "puppeteer": "^9.0.0", 22 | "request": "^2.88.2", 23 | "request-promise": "^4.2.6", 24 | "socket.io": "^4.0.1", 25 | "validator": "^13.6.0" 26 | }, 27 | "devDependencies": { 28 | "nodemon": "^2.0.7" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/middleware/Auth.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken"); 2 | const Users = require("../models/User"); 3 | const mongoose = require("mongoose"); 4 | 5 | const auth = async (req, res, next) => { 6 | try { 7 | const token = req.header("Authorization").split(" ")[1]; 8 | const decodedKey = jwt.verify(token, process.env.JWT_SECRET); 9 | 10 | const user = await Users.findOne({ 11 | _id: decodedKey._id, 12 | token: token, 13 | Deleted: false, 14 | }); 15 | if (!user) throw new Error(); 16 | 17 | req.token = token; 18 | req.user = user; 19 | req.Admin = false; 20 | 21 | if(req.user.SuperUser){ 22 | req.Admin = true; 23 | } 24 | 25 | next(); 26 | } catch (e) { 27 | res.status(401).send({ error: "Please Authenticate !" }); 28 | } 29 | }; 30 | 31 | module.exports = auth; 32 | -------------------------------------------------------------------------------- /src/socketFunc/chat.js: -------------------------------------------------------------------------------- 1 | const { getUser } = require("../utils/Users"); 2 | 3 | module.exports = function (io) { 4 | try { 5 | io.on("connection", (socket) => { 6 | socket.on("clientMsg", ({ message }) => { 7 | try { 8 | const user = getUser(socket.id); 9 | if (!user) { 10 | return; 11 | } 12 | const data = { text: message, user: user.username }; 13 | 14 | io.to(user.room).emit("serverMsg", data); 15 | } catch (e) { 16 | console.log(e); 17 | } 18 | }); 19 | socket.on("Contest-Msg", ({ message, room, name }) => { 20 | try { 21 | const data = { text: message, user: name }; 22 | io.to(room).emit("serverMsg", data); 23 | } catch (e) { 24 | console.log(e); 25 | } 26 | }); 27 | }); 28 | } catch (e) { 29 | console.log(e); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [12.x, 14.x, 16.x] 20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v2 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | cache: 'npm' 29 | - run: npm ci 30 | - run: npm run build --if-present 31 | - run: npm test 32 | -------------------------------------------------------------------------------- /src/models/Comment.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const commentSchema = new mongoose.Schema( 4 | { 5 | Body: { 6 | type: String, 7 | required: true, 8 | }, 9 | Blog: { 10 | type: mongoose.Schema.Types.ObjectId, 11 | required: true, 12 | ref: "Blog", 13 | }, 14 | User: { 15 | type: mongoose.Schema.Types.ObjectId, 16 | required: true, 17 | ref: "User", 18 | }, 19 | Deleted: { 20 | type: Boolean, 21 | default: false, 22 | }, 23 | Likes: [ 24 | { 25 | type: mongoose.Schema.Types.ObjectId, 26 | ref: "User", 27 | required: true, 28 | }, 29 | ], 30 | Replies: [ 31 | { 32 | type: mongoose.Schema.Types.ObjectId, 33 | ref: "Reply", 34 | required: true, 35 | }, 36 | ], 37 | }, 38 | { 39 | timestamps: true, 40 | } 41 | ); 42 | 43 | const table = mongoose.model("Comment", commentSchema); 44 | 45 | module.exports = table; 46 | -------------------------------------------------------------------------------- /src/models/Blogs.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const validator = require("validator"); 3 | const Comment = require("./Comment"); 4 | const User = require("./User"); 5 | 6 | const blogSchema = new mongoose.Schema( 7 | { 8 | Body: { 9 | type: String, 10 | required: true, 11 | }, 12 | User: { 13 | type: mongoose.Schema.Types.ObjectId, 14 | required: true, 15 | ref: "User", 16 | }, 17 | Deleted: { 18 | type: Boolean, 19 | default: false, 20 | }, 21 | LikesLength:{ 22 | type:Number, 23 | default:0 24 | }, 25 | Likes: [ 26 | { 27 | type: mongoose.Schema.Types.ObjectId, 28 | ref: "User", 29 | required: true, 30 | }, 31 | ], 32 | Comments: [ 33 | { 34 | type: mongoose.Schema.Types.ObjectId, 35 | ref: "Comment", 36 | required: true, 37 | }, 38 | ], 39 | }, 40 | { 41 | timestamps: true, 42 | } 43 | ); 44 | 45 | const table = mongoose.model("Blog", blogSchema); 46 | 47 | module.exports = table; 48 | -------------------------------------------------------------------------------- /src/Function/Oauth2.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | 3 | function getAuthUrl() { 4 | //defining the access scopes for google oauth 5 | const scopes = [ 6 | "https://www.googleapis.com/auth/userinfo.profile", 7 | "https://www.googleapis.com/auth/userinfo.email", 8 | ]; 9 | 10 | //Generating the authentication url 11 | return oauth2Client.generateAuthUrl({ 12 | access_type: "offline", 13 | prompt: "consent", 14 | scope: scopes, 15 | }); 16 | } 17 | 18 | //Fetching the google user using the token from the authentication url 19 | async function getGoogleUser(token) { 20 | try { 21 | const googleUser = await axios.get( 22 | `https://www.googleapis.com/oauth2/v1/userinfo?alt=json&access_token=${token.access_token}`, 23 | { 24 | headers: { 25 | Authorization: `Bearer ${token.id_token}`, 26 | }, 27 | } 28 | ); 29 | 30 | return googleUser.data; 31 | } catch (e) { 32 | throw new Error("Problem getting user"); 33 | } 34 | } 35 | 36 | module.exports = { 37 | getAuthUrl: getAuthUrl, 38 | getGoogleUser: getGoogleUser, 39 | }; 40 | -------------------------------------------------------------------------------- /src/socketFunc/Compile.js: -------------------------------------------------------------------------------- 1 | const { getUser } = require("../utils/Users"); 2 | const { compilerFunc } = require("../Function/compilerFunc"); 3 | 4 | module.exports = function (io) { 5 | try { 6 | io.on("connection", (socket) => { 7 | socket.on("Compile_ON", ({ language, code, input, reason }) => { 8 | try { 9 | const sids = io.of("/").adapter.sids; 10 | const room = [...sids.get(socket.id)][1]; //Get all t 11 | 12 | if (!room) { 13 | return; 14 | } 15 | 16 | if (reason === "code-editor") 17 | socket.broadcast.to(room).emit("Compile_ON"); 18 | 19 | compilerFunc(language, code, input) 20 | .then((res) => { 21 | if (reason === "code-editor") 22 | io.to(room).emit("COMPILE_OFF", res.data); 23 | else io.to(socket.id).emit("COMPILE_OFF", res.data); 24 | }) 25 | .catch((e) => { 26 | if (reason === "code-editor") 27 | io.to(room).emit("COMPILE_OFF", e.data); 28 | else io.to(socket.id).emit("COMPILE_OFF", res.data); 29 | }); 30 | } catch (e) { 31 | console.log(e); 32 | } 33 | }); 34 | }); 35 | } catch (e) { 36 | console.log(e); 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const http = require("http"); 3 | const socketIo = require("socket.io"); 4 | const port = process.env.PORT || 8080; 5 | const cors = require("cors"); 6 | require("./db/mongoose"); 7 | const userRouter = require("./routers/UserRouter"); 8 | const blogRouter = require("./routers/BlogRouter"); 9 | const replyRouter = require("./routers/ReplyRouter"); 10 | const commentRouter = require("./routers/CommentRouter"); 11 | const oAuthRouter = require("./routers/OauthRouter"); 12 | //const KEY = require("../config"); 13 | const app = express(); 14 | //Initialising the server instance 15 | const server = http.createServer(app); 16 | 17 | //Applying the middlewares 18 | app.use( 19 | cors({ 20 | "origin": process.env.CORS_ORIGIN, 21 | "methods": "GET,HEAD,PUT,PATCH,POST,DELETE", 22 | "preflightContinue": false, 23 | "optionsSuccessStatus": 200 24 | }) 25 | ); 26 | 27 | app.use(express.json()); 28 | //Server waking route 29 | app.get('/',(req,res)=>{ 30 | return res.status(200).send({ title: 'Waking Call..' }); 31 | }) 32 | app.use("/user", userRouter); 33 | app.use("/Oauth", oAuthRouter); 34 | app.use("/blogs", blogRouter); 35 | app.use("/reply", replyRouter); 36 | app.use("/comment", commentRouter); 37 | 38 | //Initialising a socketio instance 39 | const io = socketIo(server, { 40 | cors: { 41 | origin: "*", 42 | }, 43 | }); 44 | 45 | require("./socketFunc/userJoin")(io); 46 | require("./socketFunc/chat")(io); 47 | require("./socketFunc/Compile")(io); 48 | require("./socketFunc/problem")(io); 49 | require("./socketFunc/Contest-Join")(io); 50 | 51 | server.listen(port, () => console.log(`Listening on port ${port}`)); 52 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | **Description** 2 | 3 | 4 | 5 | **Related Issue** 6 | - issue goes here 7 | 8 | Fixes # [ISSUE] 9 | 12 | 13 | - [ ] Code 14 | - [ ] User Interface 15 | - [ ] New Feature 16 | - [ ] Documentation 17 | - [ ] Testing 18 | 19 | 20 | **Code/Quality Assurance Only** 21 | 22 | - Bug fix (non-breaking change which fixes an issue) 23 | - This change requires a documentation update (software upgrade on readme file) 24 | 25 | **How Has This Been Tested?** 26 | 27 | 28 | 29 | 30 | **Additional Info (OPTIONAL)** 31 | 32 | 33 | 34 | 35 | **Checklist** 36 | 37 | 41 | 42 | - [ ] My code follows the code style of this project. 43 | - [ ] My UI is responsive 44 | - [ ] My change requires a change to the documentation. 45 | - [ ] I have updated the documentation accordingly. 46 | - [ ] All new and existing tests passed. 47 | - [ ] My changes generate no new warnings 48 | - [ ] The title of my pull request is a short description of the requested changes. 49 | 50 | 51 | 52 | 53 | **Screenshots** 54 | Original | Updated 55 | :------------------------:|---------------------: 56 | ** original screenshot ** | ** updated screenshot ** 57 | -------------------------------------------------------------------------------- /src/routers/OauthRouter.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = new express.Router(); 3 | const { google } = require("googleapis"); 4 | const { getAuthUrl, getGoogleUser } = require("../Function/Oauth2"); 5 | const User = require("../models/User"); 6 | 7 | //Initialising the oauth client as a global variable 8 | 9 | global.oauth2Client = new google.auth.OAuth2( 10 | process.env.GOOGLE_CLIENT_ID, 11 | process.env.GOOGLE_CLIENT_SECRET, 12 | process.env.redirect_URI 13 | ); 14 | 15 | router.get("/googleOauth", async (req, res) => { 16 | try { 17 | const authUrl = getAuthUrl(); 18 | res.status(200).send(authUrl); 19 | } catch (e) { 20 | console.log(e); 21 | res.status(400).send(); 22 | } 23 | }); 24 | 25 | router.post("/authenticated", async (req, res) => { 26 | const code = req.body.code; 27 | console.log(req.body); 28 | if (!code) { 29 | return res 30 | .status(400) 31 | .send({ error: "There is a problem ! Please try again later" }); 32 | } 33 | 34 | try { 35 | const { tokens } = await oauth2Client.getToken(code); 36 | const user = await getGoogleUser(tokens); 37 | const isUser = await User.findOne({ Email: user.email, Verified: true }); 38 | if (isUser) { 39 | if (isUser.Deleted) { 40 | isUser.Deleted = false; 41 | await isUser.save(); 42 | } 43 | const token = await isUser.generateToken(); 44 | return res.status(200).send({ user: isUser, token: token, Way: "login" }); 45 | } 46 | const newUser = new User({ 47 | Name: user.name, 48 | Email: user.email, 49 | Verified: user.verified_email, 50 | }); 51 | 52 | const _Guser = await newUser.save(); 53 | const token = await _Guser.generateToken(); 54 | 55 | return res.status(200).send({ user: _Guser, token: token, Way: "signup" }); 56 | } catch (e) { 57 | res.status(400).send(); 58 | } 59 | }); 60 | 61 | module.exports = router; 62 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | * Create every pull request on develop branch 4 | * Follow our [Commit Message Style Guide]() when you commit your changes. 5 | * Please consider raising an issue before submitting a pull request (PR) to solve a problem that is not present in our [issue tracker](https://github.com/atharmohammad/ 6 | Code-N-Collab-Server/issues). This allows maintainers to first validate the issue you are trying to solve and also reference the PR to a specific issue. 7 | * When submitting a PR, please follow [this template]() (which will probably be already filled up once you create the PR). 8 | * When submitting a PR with changes to user interface (e.g.: new screen, ...), please add screenshots to the PR description. 9 | * When you are finished with your work, please squash your commits otherwise we will squash them on your PR (this can help us maintain a clear commit history). 10 | * Issues labeled as “good first issues” are meant for contributors who have not contributed to the project yet. Please choose other issues to contribute to, if you have already contributed to these type of issues. 11 | 12 | ## General Guidelines 13 | 14 | * If you’re just getting started work on an issue labeled “First Timers Only” in any project. 15 | * choose an open issue from the issue list, claim it in the comments, and a maintainer will assign it to you. 16 | * After approval you must make continuous notes on your progress in the issue while working. If there is not at least one comment every 3 days, the maintainer can reassign the issue. 17 | * Create a branch specific to the issue you're working on, so that you send a PR from that branch instead of the base branch on your fork. 18 | * If you’d like to create a new issue, please go through our issue list first (open as well as closed) and make sure the issues you are reporting do not replicate the existing issues. 19 | * Have a short description on what has gone wrong (like a root cause analysis and description of the fix), if that information is not already present in the issue. 20 | * If you have issues on multiple pages, report them separately. Do not combine them into a single issue. 21 | -------------------------------------------------------------------------------- /src/utils/Users.js: -------------------------------------------------------------------------------- 1 | //Maintaining a list of passwords for room and users 2 | 3 | const passwordList = []; 4 | const users = []; 5 | 6 | //Adding users in a room 7 | const addUser = ({ id, username, room, password }) => { 8 | username = username.trim().toLowerCase(); 9 | room = room.trim().toLowerCase(); 10 | password = password.trim(); 11 | 12 | if (!username || !room) { 13 | return { 14 | error: "Username and room are required", 15 | }; 16 | } 17 | 18 | const existingUser = users.find((user) => { 19 | return user.room === room && user.username === username; 20 | }); 21 | 22 | //No two user can have same name 23 | if (existingUser) { 24 | return { 25 | error: "Username is in use! Choose some other name", 26 | }; 27 | } 28 | 29 | const roomPassword = passwordList.find((roomPassword) => { 30 | return roomPassword.room === room; 31 | }); 32 | 33 | //Validating the room password 34 | 35 | if (roomPassword) { 36 | if (roomPassword.password !== password) 37 | return { error: "Password did not match !" }; 38 | } else { 39 | const p = { room, password }; 40 | passwordList.push(p); 41 | } 42 | 43 | //Updating the users list 44 | const user = { id, username, room }; 45 | users.push(user); 46 | return { user }; 47 | }; 48 | 49 | //removing the user by filtering the user id 50 | const removeUser = (id) => { 51 | const index = users.findIndex((user) => user.id === id); 52 | 53 | if (index !== -1) { 54 | return users.splice(index, 1)[0]; 55 | } else { 56 | return null; 57 | } 58 | }; 59 | 60 | //returns a user 61 | const getUser = (id) => { 62 | return users.find((user) => user.id === id); 63 | }; 64 | 65 | //Returning a list of users 66 | const getUsersInRoom = (room) => { 67 | return users.filter((user) => user.room === room); 68 | }; 69 | 70 | //removing the password from the list 71 | const removePassword = (room) => { 72 | const index = passwordList.findIndex( 73 | (roomPassword) => roomPassword.room === room 74 | ); 75 | 76 | if (index !== -1) { 77 | return passwordList.splice(index, 1)[0]; 78 | } 79 | }; 80 | 81 | module.exports = { 82 | addUser, 83 | removeUser, 84 | getUser, 85 | getUsersInRoom, 86 | removePassword, 87 | }; 88 | -------------------------------------------------------------------------------- /src/routers/UserRouter.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = new express.Router(); 3 | const User = require("../models/User"); 4 | const auth = require("../middleware/Auth"); 5 | const jwt = require("jsonwebtoken"); 6 | 7 | router.get("/Me", auth, async (req, res, next) => { 8 | try { 9 | const user = req.user; 10 | if (!user) res.status(404).send(); 11 | 12 | res.status(200).send(user); 13 | } catch (e) { 14 | res.status(400).send(); 15 | } 16 | }); 17 | 18 | router.get("/userProfile/:id", async (req, res) => { 19 | const id = req.params.id; 20 | console.log(id); 21 | try { 22 | const user = await User.findOne({ _id: id, Deleted: false }); 23 | if (!user) { 24 | return res.status(404).send({ error: "User not found !" }); 25 | } 26 | 27 | return res.status(200).send(user); 28 | } catch (e) { 29 | res.status(400).send(); 30 | } 31 | }); 32 | 33 | router.patch("/updateProfile", auth, async (req, res) => { 34 | const updates = Object.keys(req.body); 35 | const requiredUpdate = [ 36 | "Name", 37 | "CodeforcesHandle", 38 | "Designation", 39 | "Password", 40 | "Avatar", 41 | "Moto", 42 | "Country", 43 | "Linkedin", 44 | "Github", 45 | "Codeforces", 46 | "Codechef", 47 | "AtCoder", 48 | "Institution", 49 | ]; 50 | const allUpdates = updates.every((update) => requiredUpdate.includes(update)); 51 | if (!allUpdates) { 52 | console.log(updates); 53 | return res.status(400).send({ error: "Invalid updates!" }); 54 | } 55 | try { 56 | updates.forEach((update) => (req.user[update] = req.body[update])); 57 | await req.user.save(); 58 | 59 | res.status(200).send(req.user); 60 | } catch (e) { 61 | res.status(400).send(e); 62 | } 63 | }); 64 | 65 | router.delete("/deleteUser", auth, async (req, res) => { 66 | try { 67 | req.user.Deleted = true; 68 | await req.user.save(); 69 | res.status(200).send(); 70 | } catch (e) { 71 | res 72 | .status(400) 73 | .send({ error: "there is some error , user cannot be deleted !" }); 74 | } 75 | }); 76 | 77 | router.get("/logout", auth, async (req, res) => { 78 | try { 79 | req.user.token = null; 80 | await req.user.save(); 81 | res.status(200).send(); 82 | } catch (e) { 83 | res.status(400).send(); 84 | } 85 | }); 86 | 87 | module.exports = router; 88 | -------------------------------------------------------------------------------- /src/Function/compilerFunc.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | const { AwesomeKey } = require("./getKey"); 3 | 4 | const languageMapper = (lang_mode) => { 5 | switch (lang_mode) { 6 | case "cpp": 7 | return { language: "cpp14", versionIndex: "3" }; 8 | case "python": 9 | return { language: "python3", versionIndex: "3" }; 10 | case "java": 11 | return { language: "java", versionIndex: "3" }; 12 | case "kotlin": 13 | return { language: "kotlin", versionIndex: "2" }; 14 | case "sql": 15 | return { language: "sql", versionIndex: "3" }; 16 | case "go": 17 | return { language: "go", versionIndex: "3" }; 18 | case "scala": 19 | return { language: "scala", versionIndex: "3" }; 20 | case "shell": 21 | return { language: "bash", versionIndex: "3" }; 22 | case "pascal": 23 | return { language: "pascal", versionIndex: "2" }; 24 | case "csharp": 25 | return { language: "csharp", versionIndex: "3" }; 26 | case "php": 27 | return { language: "php", versionIndex: "3" }; 28 | case "perl": 29 | return { language: "perl", versionIndex: "3" }; 30 | case "ruby": 31 | return { language: "ruby", versionIndex: "3" }; 32 | case "swift": 33 | return { language: "swift", versionIndex: "3" }; 34 | case "lua": 35 | return { language: "lua", versionIndex: "2" }; 36 | case "rust": 37 | return { language: "rust", versionIndex: "3" }; 38 | case "r": 39 | return { language: "r", versionIndex: "3" }; 40 | case "nodejs": 41 | return { language: "nodejs", versionIndex: "3" }; 42 | } 43 | 44 | return {}; 45 | }; 46 | 47 | const compilerFunc = async (lang, code, input) => { 48 | const { language, versionIndex } = languageMapper(lang); 49 | 50 | const url = "https://api.jdoodle.com/v1/execute"; 51 | const [clientId,clientSecret] = AwesomeKey(); 52 | 53 | const sendData = { 54 | clientId, 55 | clientSecret, 56 | script: code, 57 | stdin: input, 58 | language, 59 | versionIndex, 60 | }; 61 | 62 | let response = {}; 63 | try { 64 | response = await axios({ 65 | method: "post", //you can set what request you want to be 66 | url, 67 | data: sendData, 68 | }); 69 | } catch (e) { 70 | response = e; 71 | return { 72 | data: { 73 | e: 74 | "Error:404\nOops Something went wrong\n😢😞🙁", 75 | }, 76 | }; 77 | } 78 | return response; 79 | }; 80 | 81 | module.exports = { 82 | compilerFunc, 83 | }; 84 | -------------------------------------------------------------------------------- /src/socketFunc/problem.js: -------------------------------------------------------------------------------- 1 | const { getUser } = require("../utils/Users"); 2 | const puppeteer = require("puppeteer"); 3 | 4 | //Site parameters 5 | const params = { 6 | codeforces: { 7 | querySelector: `#pageContent > div.problemindexholder > div.ttypography`, 8 | wait: 0, 9 | }, 10 | codechef: { 11 | querySelector: 12 | "#content-regions > section.content-area.small-8.columns.pl0", 13 | wait: 0, 14 | }, 15 | geeksforgeeks: { 16 | querySelector: "#problems > div.problem-statement", 17 | wait: 500, 18 | }, 19 | atcoder: { 20 | querySelector: "#task-statement > span > span.lang-en", 21 | wait: 500, 22 | }, 23 | cses: { 24 | querySelector: "body > div.skeleton > div.content-wrapper > div.content", 25 | wait: 2500, 26 | }, 27 | codedrills: { querySelector: ".py-5", wait: 5000 }, 28 | }; 29 | 30 | module.exports = function (io) { 31 | try { 32 | io.on("connection", (socket) => { 33 | socket.on("codeforces-problem", async (link) => { 34 | try { 35 | let problem = ""; 36 | if (link == null || link == undefined) { 37 | //console.log("link not defined"); 38 | } else if (link.includes("codeforces.com")) { 39 | problem = await chromiumFetch("codeforces", link); 40 | } else if (link.includes("codechef.com")) { 41 | problem = await chromiumFetch("codechef", link); 42 | } else if (link.includes("geeksforgeeks.org")) { 43 | problem = await chromiumFetch("geeksforgeeks", link); 44 | } else if (link.includes("atcoder.jp")) { 45 | problem = await chromiumFetch("atcoder", link); 46 | } else if (link.includes("cses.fi")) { 47 | problem = await chromiumFetch("cses", link); 48 | } else if (link.includes("codedrills.io")) { 49 | problem = await chormiumFetch("codedrills", link); 50 | } else { 51 | problem = ""; 52 | } 53 | 54 | const user = getUser(socket.id); 55 | if (!user) { 56 | return; 57 | } 58 | if (!problem) { 59 | problem = `
60 | Please input correct url of the problem !
And make sure Url is from following websites only: geeksforgeeks , codeforces , codechef , atcoder,cses, codeDrills 61 |
`; 62 | } 63 | io.to(user.room).emit("problem", problem); 64 | } catch (e) { 65 | console.log(e); 66 | } 67 | }); 68 | }); 69 | } catch (e) { 70 | console.log(e); 71 | } 72 | }; 73 | //Scrapping the problem from the selector after loading the page 74 | async function chromiumFetch(site, URL) { 75 | try { 76 | const browser = await puppeteer.launch({ 77 | headless: true, 78 | args: ["--no-sandbox", "--disable-setuid-sandbox"], 79 | }); 80 | console.log(site); 81 | const page = await browser.newPage(); 82 | await page.goto(URL); 83 | let text = ""; 84 | await page.waitForTimeout(params[site].wait); 85 | text = await page.evaluate((q) => { 86 | return document.querySelector(q).outerHTML; 87 | }, params[site].querySelector); 88 | 89 | await browser.close(); 90 | 91 | return text; 92 | } catch (e) { 93 | console.log(e); 94 | } 95 | 96 | return "Error: Try again :( "; 97 | } 98 | -------------------------------------------------------------------------------- /src/models/User.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const validator = require("validator"); 3 | const bcrypt = require("bcryptjs"); 4 | const jwt = require("jsonwebtoken"); 5 | 6 | const userSchema = new mongoose.Schema( 7 | { 8 | Name: { 9 | type: String, 10 | trim: true, 11 | required: true, 12 | }, 13 | Email: { 14 | type: String, 15 | trim: true, 16 | unique: true, 17 | validate: (value) => { 18 | if (!validator.isEmail(value)) throw new Error("Email is not valid !"); 19 | }, 20 | }, 21 | Password: { 22 | type: String, 23 | trim: true, 24 | }, 25 | CodeforcesHandle: { 26 | type: String, 27 | trim: true, 28 | default: null, 29 | }, 30 | Designation: { 31 | type: String, 32 | trim: true, 33 | }, 34 | Deleted: { 35 | type: Boolean, 36 | default: false, 37 | }, 38 | Blogs: [ 39 | { 40 | type: mongoose.Schema.Types.ObjectId, 41 | required: true, 42 | ref: "Blog", 43 | }, 44 | ], 45 | token: { 46 | type: String, 47 | }, 48 | Avatar: { 49 | type: Number, 50 | default: 1, 51 | }, 52 | Verified: { 53 | type: Boolean, 54 | default: false, 55 | }, 56 | Moto: { 57 | type: String, 58 | default: "", 59 | }, 60 | Institution: { 61 | type: String, 62 | default: null, 63 | }, 64 | Country: { 65 | type: String, 66 | default: "", 67 | }, 68 | Linkedin: { 69 | type: String, 70 | default: null, 71 | }, 72 | Github: { 73 | type: String, 74 | default: null, 75 | }, 76 | Codeforces: { 77 | type: String, 78 | default: null, 79 | }, 80 | Codechef: { 81 | type: String, 82 | default: null, 83 | }, 84 | AtCoder: { 85 | type: String, 86 | default: null, 87 | }, 88 | SuperUser: { 89 | type: Boolean, 90 | default: false, 91 | }, 92 | }, 93 | { 94 | timestamps: true, 95 | } 96 | ); 97 | 98 | userSchema.methods.toJSON = function () { 99 | const user = this; 100 | const userObject = user.toObject(); 101 | 102 | delete userObject.Password; 103 | delete userObject.token; 104 | delete userObject.Blogs; 105 | delete userObject.Verified; 106 | delete userObject.Deleted; 107 | 108 | return userObject; 109 | }; 110 | 111 | userSchema.methods.generateToken = async function () { 112 | const user = this; 113 | const token = jwt.sign({ _id: user._id.toString() }, process.env.JWT_SECRET); 114 | user.token = token; 115 | await user.save(); 116 | return token; 117 | }; 118 | 119 | userSchema.statics.findByCredentials = async function (email, password) { 120 | const user = await this.model("User").findOne({ 121 | Email: email, 122 | Deleted: false, 123 | }); 124 | if (!user) throw new Error("User does not exists"); 125 | 126 | const isMatch = await bcrypt.compare(password, user.Password); 127 | 128 | if (!isMatch) throw new Error("Password is not correct"); 129 | 130 | return user; 131 | }; 132 | 133 | userSchema.pre("save", async function (next) { 134 | const user = this; 135 | if (user.isModified("Password")) { 136 | user.Password = await bcrypt.hash(user.Password, 8); //Hash it 8 times 137 | } 138 | next(); 139 | }); 140 | 141 | const table = mongoose.model("User", userSchema); 142 | 143 | module.exports = table; 144 | -------------------------------------------------------------------------------- /src/routers/ReplyRouter.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = new express.Router(); 3 | 4 | const Reply = require("../models/Reply"); 5 | const Comments = require("../models/Comment"); 6 | 7 | const auth = require("../middleware/Auth"); 8 | 9 | router.post("/newReply/:id", auth, async (req, res) => { 10 | try { 11 | if(!req.body.Body || !req.body.Body.trim()){ 12 | throw new Error('Reply cant be empty!') 13 | } 14 | const reply = new Reply({ 15 | Body: req.body.Body, 16 | User: req.user._id, 17 | Comment: req.params.id, 18 | }); 19 | const comment = await Comments.findOne({ 20 | _id: req.params.id, 21 | Deleted: false, 22 | }); 23 | if (!comment) res.status(404).send(); 24 | 25 | const newReply = await reply.save(); 26 | comment.Replies.push(newReply._id); 27 | await comment.save(); 28 | res.status(200).send(newReply); 29 | } catch (e) { 30 | res.status(400).send(); 31 | } 32 | }); 33 | 34 | router.get("/getReply/:id", async (req, res) => { 35 | try { 36 | const comment = await Comments.findOne({ 37 | _id: req.params.id, 38 | Deleted: false, 39 | }) 40 | .populate({ 41 | path: "Replies", 42 | model: "Reply", 43 | select: ["User", "Body", "Likes", "Comment","createdAt"], 44 | match: { Deleted: false }, 45 | populate: { 46 | path: "User", 47 | select: ["Name", "Designation", "Avatar", "Institution","SuperUser"], 48 | }, 49 | }) 50 | .exec(); 51 | 52 | if (!comment) { 53 | return res.status(400).send(); 54 | } 55 | 56 | res.status(200).send(comment); 57 | } catch (e) { 58 | console.log(e); 59 | res.status(400).send(); 60 | } 61 | }); 62 | 63 | router.post("/like/:id", auth, async (req, res) => { 64 | try { 65 | const reply = await Reply.findOne({ _id: req.params.id, Deleted: false }); 66 | if (!reply) res.status(404).send(); 67 | 68 | const like = reply.Likes.find((curr) => { 69 | return curr.toString().trim() == req.user._id.toString().trim(); 70 | }); 71 | 72 | if (like) { 73 | const likeArray = reply.Likes.filter( 74 | (curr) => curr.toString().trim() != req.user._id.toString().trim() 75 | ); 76 | reply.Likes = likeArray; 77 | } else { 78 | reply.Likes.push(req.user._id); 79 | } 80 | const newReply = await reply.save(); 81 | res.status(200).send(newReply); 82 | } catch (e) { 83 | res.status(400).send(); 84 | } 85 | }); 86 | 87 | router.delete("/deleteReply/:id", auth, async (req, res) => { 88 | const id = req.params.id; 89 | 90 | try { 91 | const reply = await Reply.findOne({ _id: id, Deleted: false }); 92 | if (!reply) return res.status(404).send(); 93 | 94 | if (!req.Admin && reply.User.toString().trim() !== req.user._id.toString().trim()) { 95 | return res.status(401).send(); 96 | } 97 | 98 | reply.Deleted = true; 99 | await reply.save(); 100 | res.status(200).send(); 101 | } catch (e) { 102 | res.status(400).send(); 103 | } 104 | }); 105 | 106 | router.patch("/updateReply/:id", auth, async (req, res) => { 107 | const id = req.params.id; 108 | 109 | try { 110 | const reply = await Reply.findOne({ _id: id, Deleted: false }); 111 | 112 | if (!reply) { 113 | return res.status(404).send(); 114 | } 115 | 116 | if (reply.User.toString().trim() !== req.user._id.toString().trim()) { 117 | return res.status(401).send(); 118 | } 119 | if(!req.body.Body || !req.body.Body.trim()){ 120 | throw new Error('Reply cant be empty!') 121 | } 122 | reply.Body = req.body.Body; 123 | await reply.save(); 124 | res.status(200).send(reply); 125 | } catch (e) { 126 | res.status(400).send(); 127 | } 128 | }); 129 | 130 | module.exports = router; 131 | -------------------------------------------------------------------------------- /src/routers/BlogRouter.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = new express.Router(); 3 | const Blog = require("../models/Blogs"); 4 | const auth = require("../middleware/Auth"); 5 | const User = require("../models/User"); 6 | 7 | router.get("/Allblogs", async (req, res) => { 8 | try { 9 | let sort; 10 | // /Allblogs?sortBy=popularity&skip=5 11 | if (req.query.sortBy === "popularity") { 12 | sort = { LikesLength: "desc" }; 13 | }else if(req.query.sortBy === "oldest-first"){ 14 | sort = { createdAt: 1 }; 15 | } 16 | else { 17 | sort = { createdAt: "desc" }; 18 | } 19 | 20 | const blogs = await Blog.find({ Deleted: false }) 21 | .limit(10) 22 | .skip(parseInt(req.query.skip)) 23 | .sort(sort) 24 | .populate({ 25 | path: "User", 26 | select: ["Name", "Designation", "Avatar", "Institution", "SuperUser"], 27 | match: { Deleted: false }, 28 | }) 29 | .exec(); 30 | res.status(200).send(blogs); 31 | } catch (e) { 32 | res.status(400).send(e); 33 | } 34 | }); 35 | 36 | router.post("/write", auth, async (req, res) => { 37 | try { 38 | if(!req.body.Body || !req.body.Body.trim()){ 39 | throw new Error('Blog cant be empty!') 40 | } 41 | const blog = new Blog({ Body: req.body.Body, User: req.user._id }); 42 | const newBlog = await blog.save(); 43 | const user = await User.findOne({ _id: req.user._id }); 44 | user.Blogs.push(newBlog._id); 45 | await user.save(); 46 | res.status(200).send(newBlog); 47 | } catch (e) { 48 | res.status(400).send(e); 49 | } 50 | }); 51 | 52 | router.delete("/delete/:id", auth, async (req, res) => { 53 | try { 54 | const _id = req.params.id; 55 | const blog = await Blog.findOne({ _id: _id, Deleted: false }); 56 | if (!blog) return res.status(404).send(); 57 | 58 | if ( 59 | !req.Admin && 60 | blog.User.toString().trim() != req.user._id.toString().trim() 61 | ) { 62 | return res.status(401).send(); 63 | } 64 | 65 | blog.Deleted = true; 66 | await blog.save(); 67 | 68 | res.status(200).send(); 69 | } catch (e) { 70 | return res.status(400).send(e); 71 | } 72 | }); 73 | 74 | router.get("/currentBlog/:id", async (req, res) => { 75 | try { 76 | const id = req.params.id; 77 | const blog = await Blog.findOne({ _id: id, Deleted: false }).populate({ 78 | path: "User", 79 | select: ["Name", "Designation", "Avatar", "Institution","SuperUser"], 80 | match: { Deleted: false }, 81 | }); 82 | if (!blog) res.status(404).send(); 83 | 84 | res.status(200).send(blog); 85 | } catch (e) { 86 | return res.status(400).send(e); 87 | } 88 | }); 89 | 90 | router.patch("/currBlog/:id", auth, async (req, res) => { 91 | try { 92 | const id = req.params.id; 93 | const blog = await Blog.findOne({ _id: id, Deleted: false }); 94 | if (!blog) res.status(404).send(); 95 | 96 | if(!req.body.Body || !req.body.Body.trim()){ 97 | throw new Error('Blog cant be empty!') 98 | } 99 | 100 | blog.Body = req.body.Body; 101 | await blog.save(); 102 | res.status(200).send(); 103 | } catch (e) { 104 | res.status(400).send(e); 105 | } 106 | }); 107 | 108 | router.post("/like/:id", auth, async (req, res) => { 109 | try { 110 | const blog = await Blog.findOne({ 111 | _id: req.params.id, 112 | Deleted: false, 113 | }); 114 | 115 | if (!blog) { 116 | return res.status(404).send(); 117 | } 118 | 119 | const like = blog.Likes.find((curr) => { 120 | return curr.toString().trim() == req.user._id.toString().trim(); 121 | }); 122 | 123 | if (like) { 124 | const likeArray = blog.Likes.filter( 125 | (curr) => curr.toString().trim() != req.user._id.toString().trim() 126 | ); 127 | blog.Likes = likeArray; 128 | } else { 129 | blog.Likes.push(req.user._id); 130 | } 131 | 132 | blog.LikesLength = blog.Likes.length; 133 | 134 | await blog.save(); 135 | res.status(200).send(blog); 136 | } catch (e) { 137 | console.log(e); 138 | res.status(400).send(); 139 | } 140 | }); 141 | 142 | module.exports = router; 143 | -------------------------------------------------------------------------------- /src/routers/CommentRouter.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = new express.Router(); 3 | const Comments = require("../models/Comment"); 4 | const Blogs = require("../models/Blogs"); 5 | const auth = require("../middleware/Auth"); 6 | 7 | router.get("/getComments/:id", async (req, res) => { 8 | const id = req.params.id; 9 | try { 10 | const blogs = await Blogs.findOne({ _id: id, Deleted: false }) 11 | .populate({ 12 | path: "Comments", 13 | model: "Comment", 14 | select: ["User", "Body", "Likes", "Replies","createdAt"], 15 | match: { Deleted: false }, 16 | populate: { 17 | path: "User", 18 | select: ["Name", "Designation", "Avatar", "Institution","SuperUser"], 19 | }, 20 | }) 21 | .exec(); 22 | 23 | if (!blogs) { 24 | res.status(404).send(); 25 | } else { 26 | res.status(200).send(blogs); 27 | } 28 | } catch (e) { 29 | console.log(e); 30 | res.status(404).send(); 31 | } 32 | }); 33 | 34 | router.post("/createComment/:id", auth, async (req, res) => { 35 | try { 36 | const blog = await Blogs.findOne({ _id: req.params.id }); 37 | 38 | if (!blog) { 39 | return res.status(404).send(); 40 | } 41 | if(!req.body.Body || !req.body.Body.trim()){ 42 | throw new Error('Comment cant be empty!') 43 | } 44 | const comment = new Comments({ 45 | Body: req.body.Body, 46 | User: req.user._id, 47 | Blog: req.params.id, 48 | }); 49 | 50 | const newComment = await comment.save(); 51 | blog.Comments.push(newComment._id); 52 | await blog.save(); 53 | res.status(200).send(); 54 | } catch (e) { 55 | res.status(400).send(e); 56 | } 57 | }); 58 | 59 | router.post("/like/:id", auth, async (req, res) => { 60 | try { 61 | const comment = await Comments.findOne({ 62 | _id: req.params.id, 63 | Deleted: false, 64 | }); 65 | if (!comment) throw new Error(); 66 | 67 | const like = comment.Likes.find((curr) => { 68 | return curr.toString().trim() == req.user._id.toString().trim(); 69 | }); 70 | if (like) { 71 | const likeArray = comment.Likes.filter( 72 | (curr) => curr.toString().trim() != req.user._id.toString().trim() 73 | ); 74 | comment.Likes = likeArray; 75 | } else { 76 | comment.Likes.push(req.user._id); 77 | } 78 | 79 | await comment.save(); 80 | res.status(200).send(comment); 81 | } catch (e) { 82 | res.status(400).send(e); 83 | } 84 | }); 85 | 86 | router.patch("/updateComment/:id", auth, async (req, res) => { 87 | const id = req.params.id; 88 | try { 89 | const comment = await Comments.findOne({ _id: id, Deleted: false }); 90 | if (!comment) { 91 | return res.status(404).send(); 92 | } 93 | 94 | if (comment.User.toString().trim() !== req.user._id.toString().trim()) { 95 | return res.status(401).send(); 96 | } 97 | 98 | if(!req.body.Body || !req.body.Body.trim()){ 99 | throw new Error('Comment cant be empty!') 100 | } 101 | 102 | comment.Body = req.body.Body; 103 | await comment.save(); 104 | res.status(200).send(comment.Body); 105 | } catch (e) { 106 | res.status(400).send(e); 107 | } 108 | }); 109 | 110 | router.delete("/deleteComment/:id", auth, async (req, res) => { 111 | const id = req.params.id; 112 | 113 | try { 114 | const comment = await Comments.findOne({ _id: id, Deleted: false }); 115 | if (!comment) return res.status(404).send(); 116 | 117 | if (!req.Admin && comment.User.toString().trim() !== req.user._id.toString().trim()) { 118 | return res.status(401).send(); 119 | } 120 | const blog = await Blogs.findOne({ 121 | _id: comment.Blog.toString().trim(), 122 | Deleted: false, 123 | }); 124 | const newCommentArray = blog.Comments.filter( 125 | (comment) => comment.toString().trim() !== id.toString().trim() 126 | ); 127 | blog.Comments = newCommentArray; 128 | await blog.save(); 129 | comment.Deleted = true; 130 | await comment.save(); 131 | res.status(200).send(); 132 | } catch (e) { 133 | res.status(400).send(); 134 | } 135 | }); 136 | 137 | module.exports = router; 138 | -------------------------------------------------------------------------------- /src/socketFunc/Contest-Join.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | const { 3 | checkContest, 4 | removeContestUser, 5 | startContest, 6 | getTeamMembers, 7 | createURL, 8 | updateContest, 9 | getContestLength, 10 | deleteContests, 11 | } = require("../utils/Contest"); 12 | 13 | let DeleteIntervalOn = false; 14 | 15 | module.exports = function (io) { 16 | try { 17 | io.on("connection", (socket) => { 18 | socket.on("Contest-Join", (user, callback) => { 19 | try { 20 | if (!user || !user.Name || !user.Name.trim()) { 21 | return callback({ 22 | error: "Update Codeforces Handle", 23 | contest: null, 24 | }); 25 | } 26 | if (!user.RoomId || !user.RoomId.trim()) { 27 | return callback({ error: "Invalid room Name", contest: null }); 28 | } 29 | 30 | const obj = checkContest(user.RoomId, user.Name, socket.id); 31 | console.log(obj.contest); 32 | if (obj.error) { 33 | return callback({ error: obj.error, contest: obj.contest }); 34 | } else { 35 | if (!DeleteIntervalOn) { 36 | console.log("Starting Interval"); 37 | DeleteIntervalOn = true; 38 | const interval = setInterval(() => { 39 | console.log("deleting data....."); 40 | deleteContests(); 41 | if (getContestLength() == 0) { 42 | console.log("Stopping Interval"); 43 | DeleteIntervalOn = false; 44 | clearInterval(interval); 45 | } 46 | }, 24 * 60 * 60 * 1000); 47 | } 48 | socket.join(user.RoomId); 49 | console.log("contest-joined"); 50 | callback({ error: obj.error, contest: obj.contest }); 51 | const teamMembers = getTeamMembers(obj.contest.UsersId); 52 | io.in(user.RoomId).emit("peopleInRoom", { 53 | teamMembers, 54 | userJoin: user.Name.trim().toLowerCase(), 55 | }); 56 | } 57 | } catch (e) { 58 | console.log(e); 59 | } 60 | }); 61 | socket.on( 62 | "Start-Contest", 63 | ({ room, problemTags, minRating, maxRating, maxDuration }) => { 64 | try { 65 | socket.to(room).emit("Contest-Starting"); 66 | problemTags = problemTags.map((tag) => tag.label); 67 | const URL = createURL(problemTags); 68 | const problems = axios 69 | .get(URL) 70 | .then((res) => { 71 | console.log(res); 72 | const problemArray = res.data.result.problems.slice(0); 73 | const contest = startContest({ 74 | room, 75 | problemTags, 76 | minRating, 77 | maxRating, 78 | problemArray, 79 | maxDuration, 80 | }); 81 | const teamMembers = getTeamMembers(contest.UsersId); 82 | io.to(room).emit("Update", contest); //First update then send memebers 83 | }) 84 | .catch((e) => console.log(e)); 85 | } catch (e) { 86 | console.log(e); 87 | } 88 | } 89 | ); 90 | socket.on("Contest-Update", async ({ roomId }) => { 91 | try { 92 | const contest = await updateContest(roomId); 93 | console.log(contest); 94 | io.to(roomId).emit("Update", contest); 95 | } catch (e) { 96 | console.log(e); 97 | } 98 | }); 99 | socket.on("Leave-Contest", (user) => { 100 | try { 101 | console.log("contest-Left"); 102 | const contest = removeContestUser({ 103 | roomId: user.roomId, 104 | name: user.name, 105 | }); 106 | console.log(contest); 107 | const teamMembers = getTeamMembers(contest.UsersId); 108 | console.log(teamMembers); 109 | io.to(user.room).emit("peopleInRoom", { 110 | teamMembers, 111 | userLeft: user.name.trim().toLowerCase(), 112 | }); 113 | socket.leave(user.room); 114 | } catch (e) { 115 | console.log(e); 116 | } 117 | }); 118 | }); 119 | } catch (e) { 120 | console.log(e); 121 | } 122 | }; 123 | -------------------------------------------------------------------------------- /src/socketFunc/userJoin.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | const { 3 | addUser, 4 | removeUser, 5 | getUser, 6 | getUsersInRoom, 7 | removePassword, 8 | } = require("../utils/Users"); 9 | 10 | module.exports = function (io) { 11 | try { 12 | io.on("connection", (socket) => { 13 | socket.on("join", ({ username, room, password }, callback) => { 14 | try { 15 | const { error, user } = addUser({ 16 | id: socket.id, 17 | username, 18 | room, 19 | password, 20 | }); 21 | 22 | if (error) { 23 | return callback({ error }); 24 | } 25 | try { 26 | socket.join(user.room); 27 | 28 | console.log("A new user joined", user.room, user.username); 29 | } catch (e) { 30 | return console.log("cant join"); 31 | } 32 | 33 | //To get data for newly connected client from the room 34 | const socketsInstances = async () => { 35 | try { 36 | const clients = await io.in(user.room).fetchSockets(); 37 | const teamMembers = getUsersInRoom(user.room); 38 | io.to(user.room).emit("peopleInRoom", { 39 | teamMembers, 40 | userJoin: user.username, 41 | }); 42 | //counts how many users are active in room 43 | let res = ""; 44 | if (clients.length > 1) { 45 | //make functions for getting data 46 | let askedCnt = 0; 47 | 48 | for (const client of clients) { 49 | if (askedCnt == 5) break; 50 | if (client.id === socket.id) continue; 51 | 52 | askedCnt++; 53 | io.to(client.id).emit("sendInitialIO", { id: socket.id }); 54 | } 55 | } 56 | } catch (e) {} 57 | }; 58 | 59 | socketsInstances(); 60 | return callback({ user }); 61 | } catch (e) { 62 | console.log(e); 63 | } 64 | }); 65 | 66 | socket.on("takeInitialIO", (data) => { 67 | try { 68 | if (data.reason === "code-editor") { 69 | console.log("takeInitialIO", data.inputText, data.outputText); 70 | io.to(data.id).emit("IO_recieved", { 71 | inputText: data.inputText, 72 | outputText: data.outputText, 73 | }); 74 | } 75 | } catch (e) { 76 | console.log(e); 77 | } 78 | }); 79 | //real-time updates on the change in IO 80 | socket.on("changeIO", (data) => { 81 | try { 82 | if (data.reason === "code-editor") { 83 | const sids = io.of("/").adapter.sids; //sids gives map of every user socketid to its room 84 | const room = [...sids.get(socket.id)][1]; 85 | if (!room) return; 86 | socket.broadcast.to(room).emit("IO_recieved", data); 87 | } 88 | } catch (e) { 89 | console.log(e); 90 | } 91 | }); 92 | //Disconnecting the user and updating , notifying other people in the room about the user 93 | socket.on("disconnect", () => { 94 | try { 95 | const user = removeUser(socket.id); 96 | 97 | if (!user) return; 98 | 99 | console.log("disconnecting", user); 100 | 101 | if (user) { 102 | try { 103 | const socketsInstances = async () => { 104 | const clients = await io.in(user.room).fetchSockets(); 105 | const teamMembers = getUsersInRoom(user.room); 106 | if (clients.length) { 107 | io.to(user.room).emit("peopleInRoom", { 108 | teamMembers, 109 | userLeft: user.username, 110 | }); 111 | } 112 | 113 | if (clients.length == 0) { 114 | removePassword(user.room); 115 | } 116 | socket.leave(user.room); 117 | console.log("Disconnected"); 118 | }; 119 | socketsInstances(); 120 | } catch (e) { 121 | console.log(e); 122 | } 123 | } 124 | } catch (e) { 125 | console.log(e); 126 | } 127 | }); 128 | }); 129 | } catch (e) { 130 | console.log(e); 131 | } 132 | }; 133 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Our Pledge 2 | 3 | We as members, contributors, and leaders pledge to make participation in our 4 | community a harassment-free experience for everyone, regardless of age, body 5 | size, visible or invisible disability, ethnicity, sex characteristics, gender 6 | identity and expression, level of experience, education, socio-economic status, 7 | nationality, personal appearance, race, religion, or sexual identity 8 | and orientation. 9 | 10 | We pledge to act and interact in ways that contribute to an open, welcoming, 11 | diverse, inclusive, and healthy community. 12 | 13 | ## Our Standards 14 | 15 | Examples of behavior that contributes to a positive environment for our 16 | community include: 17 | 18 | * Demonstrating empathy and kindness toward other people 19 | * Being respectful of differing opinions, viewpoints, and experiences 20 | * Giving and gracefully accepting constructive feedback 21 | * Accepting responsibility and apologizing to those affected by our mistakes, 22 | and learning from the experience 23 | * Focusing on what is best not just for us as individuals, but for the 24 | overall community 25 | 26 | Examples of unacceptable behavior include: 27 | 28 | * The use of sexualized language or imagery, and sexual attention or 29 | advances of any kind 30 | * Trolling, insulting or derogatory comments, and personal or political attacks 31 | * Public or private harassment 32 | * Publishing others' private information, such as a physical or email 33 | address, without their explicit permission 34 | * Other conduct which could reasonably be considered inappropriate in a 35 | professional setting 36 | 37 | ## Enforcement Responsibilities 38 | 39 | Community leaders are responsible for clarifying and enforcing our standards of 40 | acceptable behavior and will take appropriate and fair corrective action in 41 | response to any behavior that they deem inappropriate, threatening, offensive, 42 | or harmful. 43 | 44 | Community leaders have the right and responsibility to remove, edit, or reject 45 | comments, commits, code, wiki edits, issues, and other contributions that are 46 | not aligned to this Code of Conduct, and will communicate reasons for moderation 47 | decisions when appropriate. 48 | 49 | ## Scope 50 | 51 | This Code of Conduct applies within all community spaces, and also applies when 52 | an individual is officially representing the community in public spaces. 53 | Examples of representing our community include using an official e-mail address, 54 | posting via an official social media account, or acting as an appointed 55 | representative at an online or offline event. 56 | 57 | ## Enforcement 58 | 59 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 60 | reported to the community leaders responsible for enforcement at 61 | codencollab@gmail.com. 62 | All complaints will be reviewed and investigated promptly and fairly. 63 | 64 | All community leaders are obligated to respect the privacy and security of the 65 | reporter of any incident. 66 | 67 | ## Enforcement Guidelines 68 | 69 | Community leaders will follow these Community Impact Guidelines in determining 70 | the consequences for any action they deem in violation of this Code of Conduct: 71 | 72 | ### 1. Correction 73 | 74 | **Community Impact**: Use of inappropriate language or other behavior deemed 75 | unprofessional or unwelcome in the community. 76 | 77 | **Consequence**: A private, written warning from community leaders, providing 78 | clarity around the nature of the violation and an explanation of why the 79 | behavior was inappropriate. A public apology may be requested. 80 | 81 | ### 2. Warning 82 | 83 | **Community Impact**: A violation through a single incident or series 84 | of actions. 85 | 86 | **Consequence**: A warning with consequences for continued behavior. No 87 | interaction with the people involved, including unsolicited interaction with 88 | those enforcing the Code of Conduct, for a specified period of time. This 89 | includes avoiding interactions in community spaces as well as external channels 90 | like social media. Violating these terms may lead to a temporary or 91 | permanent ban. 92 | 93 | ### 3. Temporary Ban 94 | 95 | **Community Impact**: A serious violation of community standards, including 96 | sustained inappropriate behavior. 97 | 98 | **Consequence**: A temporary ban from any sort of interaction or public 99 | communication with the community for a specified period of time. No public or 100 | private interaction with the people involved, including unsolicited interaction 101 | with those enforcing the Code of Conduct, is allowed during this period. 102 | Violating these terms may lead to a permanent ban. 103 | 104 | ### 4. Permanent Ban 105 | 106 | **Community Impact**: Demonstrating a pattern of violation of community 107 | standards, including sustained inappropriate behavior, harassment of an 108 | individual, or aggression toward or disparagement of classes of individuals. 109 | 110 | **Consequence**: A permanent ban from any sort of public interaction within 111 | the community. 112 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 5 | 6 | 7 |

8 |
9 | 10 | [![Contributors][contributors-shield]][contributors-url] 11 | [![Forks][forks-shield]][forks-url] 12 | [![Stargazers][stars-shield]][stars-url] 13 | [![MIT License][license-shield]][license-url] 14 | [![Issues][issues-shield]][issues-url] 15 | [![LinkedIn][linkedin-shield]][linkedin-url] 16 | 17 |
18 | Table of Contents 19 |
    20 |
  1. 21 | About 22 | 25 |
  2. 26 |
  3. 27 | Getting Started 28 | 31 | 34 |
  4. 35 |
  5. Contributing
  6. 36 |
  7. Contact
  8. 37 |
  9. Resources
  10. 38 |
  11. License
  12. 39 |
40 |
41 | 42 | 43 | 44 | 45 | 48 | 51 | 52 | 53 | 56 | 59 | 60 |
46 | 47 | 49 | 50 |
54 | 55 | 57 | 58 |
61 |
62 | 63 | 64 | 65 | 66 | 69 | 70 |
67 | Collaboration 68 |
71 |
72 | 73 | 74 | # 🔖 About 75 | Code-N-Collab server is backend for Code-N-Collab , It make Code-N-Collab real-time collaborative using sockets and also serves as backend for blogs 76 | 77 | ### Website 78 | [Code-N-Collab](https://code-n-collab.netlify.app/) 79 | 80 | ## 🚀 Features 81 | It provides users with :- 82 | - Real-time code editor : Users can collaborate with their team on issues and solve CP problems using a real-time code-editor(like Google Docs) 83 | 84 | - Lockout Championship : For Cp lovers, they can compete in a lockout championships with their friends to and challenge your friends for championship,filter problems with preferred difficulty and improve you CP skills 85 | 86 | - Blogs : To find new people and share your knowledge , platform provides users blogs to write learn and share 87 | 88 | ## 🔥 Getting Started 89 | 90 | ### Prerequisites 91 | 92 | - Reactjs 93 | - Nodejs 94 | - Google Cloud Platform 95 | 96 | #### Setup your free GCP account for google Oauth 97 | - https://support.google.com/cloud/answer/6158849?hl=en 98 | 99 | - Setup API&Credentials for Web Application 100 | 101 | - Provide a Redirect URI in the Credentials(The redirect_URI will be used to redirect to the page after login with google,in below environment variable example we have setup redirect_URI=http://localhost:3000/homepage/ , using port 3000 you can use any port but make sure to add /homepage after that to redirect to correct route) 102 | 103 | - copy and 104 | 105 | #### Setup your MONGO_DB atlas 106 | - Atlas Docs 107 | - get the from your cluster , you have to use it in env vars 108 | 109 | #### Setup Environment variables 110 | - you can declare your env vars using dotenv like below : 111 | 112 | ``` 113 | CORS_ORIGIN=* 114 | GOOGLE_CLIENT_ID= 115 | GOOGLE_CLIENT_SECRET= 116 | redirect_URI=http://localhost:3000/homepage/ 117 | BaseURI=http://localhost:8080/ 118 | MONGO_DB_URL= 119 | COMPILE_CLIENT_ID1= 120 | COMPILE_CLIENT_SECRET1= 121 | COMPILE_CLIENT_ID2= 122 | COMPILE_CLIENT_SECRET2= 123 | COMPILE_CLIENT_ID3= 124 | COMPILE_CLIENT_SECRET3= 125 | COMPILE_CLIENT_ID4= 126 | COMPILE_CLIENT_SECRET4= 127 | 128 | ``` 129 | - or you can declare your env vars in nodemon.json if you are using nodemon for development like below: 130 | ``` 131 | { 132 | "env":{ 133 | "CORS_ORIGIN":"*", 134 | "GOOGLE_CLIENT_ID": "", 135 | "GOOGLE_CLIENT_SECRET": "", 136 | "redirect_URI" : "http://localhost:3000/homepage/", 137 | "BaseURI":"http://localhost:8080/", 138 | "MONGO_DB_URL":"", 139 | "COMPILE_CLIENT_ID1":"", 140 | "COMPILE_CLIENT_SECRET1":"", 141 | "COMPILE_CLIENT_ID2":"", 142 | "COMPILE_CLIENT_SECRET2":"", 143 | "COMPILE_CLIENT_ID3":"", 144 | "COMPILE_CLIENT_SECRET3":"", 145 | "COMPILE_CLIENT_ID4":"", 146 | "COMPILE_CLIENT_SECRET4":"" 147 | } 148 | } 149 | 150 | ``` 151 | 152 | ### Installation 153 | 154 | ``` 155 | $ git clone https://github.com//Code-N-Collab-Server.git 156 | $ cd Code-N-Collab-Server 157 | $ git remote add upstream https://github.com/atharmohammad/Code-N-Collab-Server.git 158 | $ npm install 159 | 160 | $ npm start // if you are using dotenv 161 | 162 | //or 163 | 164 | $ npm run dev // if you are using nodemon.json 165 | 166 | ``` 167 | 168 | ## 💁 Contribution guidelines 169 | 170 | we encourage organizations and individuals to contribute requirements, documentation, issues, new templates, and code. 171 | For code contributions, read : 172 | 173 | - The Code of Conduct 174 | - The Contribution Guidelines 175 | 176 | ## 📲 Contact 177 | 178 | Mohd Athar - mohd.rule123@gmail.com 179 |
180 | Adnan Shamsi - adnanshamsi023@gmail.com 181 | 182 | ## 📚 Resources 183 | - Socket.Io Documentation 184 | - Convergence Docs 185 | - Atlas Docs 186 | - React-Codemirror Editor Docs 187 | - Mongoose Docs 188 | 189 | ## License 190 | Apache License 2.0 191 | 192 | [contributors-shield]: https://img.shields.io/github/contributors/atharmohammad/Code-N-Collab-Server.svg?style=for-the-badge 193 | [contributors-url]: https://github.com/atharmohammad/Code-N-Collab-Server/graphs/contributors 194 | [forks-shield]: https://img.shields.io/github/forks/atharmohammad/Code-N-Collab-Server.svg?style=for-the-badge 195 | [forks-url]: https://github.com/atharmohammad/Code-N-Collab-Server/network/members 196 | [stars-shield]: https://img.shields.io/github/stars/atharmohammad/Code-N-Collab-Server.svg?style=for-the-badge 197 | [stars-url]: https://github.com/atharmohammad/Code-N-Collab-Server/stargazers 198 | [issues-shield]: https://img.shields.io/github/issues/atharmohammad/Code-N-Collab-Server.svg?style=for-the-badge 199 | [issues-url]: https://github.com/atharmohammad/Code-N-Collab-Server/issues 200 | [license-shield]: https://img.shields.io/github/license/atharmohammad/Code-N-Collab-Server.svg?style=for-the-badge 201 | [license-url]: https://github.com/atharmohammad/Code-N-Collab-Server/blob/master/LICENSE 202 | [linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555 203 | [linkedin-url]: https://www.linkedin.com/in/athar-mohammad-34068a157/ 204 | 205 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/utils/Contest.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | 3 | //Storing all the contest data 4 | let contests = []; 5 | 6 | //checking the contest existence and its validation 7 | const checkContest = (roomId, name, socketid) => { 8 | try{ 9 | const contest = contests.find((con, index) => con.Id === roomId); 10 | let user = undefined; 11 | let usersCnt = 0; 12 | 13 | if (contest) { 14 | user = contest.UsersId.find((user, index) => user === name); 15 | usersCnt = contest.UsersId.length; 16 | } 17 | 18 | if (!contest) { 19 | return createContest(roomId, name, socketid); 20 | } else if (contest.Started && !user) { 21 | return { error: "Contest has Already Started !", contest: null }; 22 | } else if (usersCnt == 4 && !user) { 23 | return { error: "Room is Full", contest: null }; 24 | } else if (usersCnt < 4 || user) { 25 | return joinContest(roomId, name, socketid, contest); 26 | } else { 27 | return { 28 | error: "There might be an error! Please Try again !", 29 | contest: null, 30 | }; 31 | } 32 | }catch(e){ 33 | return { 34 | error: "There might be an error! Please Try again !", 35 | contest: null, 36 | }; 37 | } 38 | }; 39 | 40 | //Creating a contest 41 | const createContest = (roomId, name, socketid) => { 42 | try{ 43 | //initialising the User object 44 | const user = { 45 | Name: name, 46 | Score: 0, 47 | SocketId: socketid, 48 | }; 49 | 50 | //initialising the contest object 51 | const lockout = { 52 | Id: roomId, 53 | Users: [user], 54 | UsersId: [name], 55 | EndTime: null, 56 | Problems: [], 57 | Started: false, 58 | problemTags: [], 59 | minRating: 0, 60 | maxRating: 0, 61 | StartTime: new Date().getTime(), 62 | userToProblem: new Map(), 63 | }; 64 | 65 | //Inserting the contest in the array 66 | contests.push(lockout); 67 | console.log("contest-created!"); 68 | return { error: null, contest: lockout }; 69 | }catch(e){ 70 | return { 71 | error: "There might be an error! Please Try again !", 72 | contest: null, 73 | }; 74 | } 75 | 76 | }; 77 | 78 | //If contest already exist and user is valid then join the contest 79 | const joinContest = (roomId, name, socketid, co) => { 80 | try{ 81 | //Finding the user , contest with in the array 82 | const contest = contests.find((con, index) => con.Id === roomId); 83 | const user = contest.Users.find((user, i) => user.Name === name); 84 | const userId = contest.UsersId.find((user, i) => user === name); 85 | 86 | //If no contest exist return 87 | if (!contest) { 88 | return; 89 | } 90 | 91 | //If both user and userId is available means the user hadn't exit the contest 92 | if (user && userId) { 93 | user.SocketId = socketid; 94 | return { error: null, contest: contest }; 95 | } else if (user) { 96 | //If user joins the contest again after exiting 97 | user.SocketId = socketid; 98 | contest.UsersId.push(name); 99 | return { error: null, contest: contest }; 100 | } 101 | 102 | //If new user joined the contest before starting 103 | const newUser = { 104 | Name: name, 105 | Score: 0, 106 | SocketId: socketid, 107 | }; 108 | 109 | contest.UsersId.push(name); 110 | contest.Users.push(newUser); 111 | 112 | console.log("Joined the existing contest"); 113 | return { error: null, contest: contest }; 114 | 115 | }catch(e){ 116 | return { 117 | error: "There might be an error! Please Try again !", 118 | contest: null, 119 | }; 120 | } 121 | }; 122 | 123 | //Removing the contest user on its exit 124 | 125 | const removeContestUser = ({ roomId, name }) => { 126 | //Finding the contest 127 | try { 128 | const contest = contests.find((con, index) => con.Id === roomId); 129 | 130 | if (!contest) { 131 | return; 132 | } 133 | 134 | //Filtering the userId for removal , user name will remain to show on leaderboard 135 | const UserIds = contest.UsersId; 136 | const users = UserIds.filter((id, i) => id !== name); 137 | contest.UsersId = users; 138 | console.log(contest, "removed user"); 139 | return contest; 140 | } catch (e) { 141 | return { 142 | error: "There might be an error! Please Try again !", 143 | contest: null, 144 | }; 145 | } 146 | }; 147 | 148 | //starting the contest and fetching problems 149 | const startContest = ({ 150 | room, 151 | problemTags, 152 | minRating, 153 | maxRating, 154 | maxDuration, 155 | problemArray, 156 | }) => { 157 | try{ 158 | ////setting up problems//// 159 | shuffle(problemArray); 160 | 161 | const problems = []; 162 | const problemLink = "https://codeforces.com/problemset/problem/"; 163 | let problemCount = 0; 164 | 165 | if (!maxDuration || maxDuration < 10 || maxDuration > 120) { 166 | maxDuration = 30; 167 | } 168 | 169 | //Selecting the problems after filtering 170 | problemArray.every((problem, i) => { 171 | if ( 172 | problem.rating >= parseInt(minRating) && 173 | problem.rating <= parseInt(maxRating) 174 | ) { 175 | const link = problemLink + problem.contestId + "/" + problem.index + "/"; 176 | problems.push({ 177 | key: i, 178 | link: link, 179 | name: problem.name, 180 | points: problem.rating, 181 | solved: false, 182 | author: null, 183 | }); 184 | problemCount++; 185 | if (problemCount == 5) return false; 186 | 187 | return true; 188 | } 189 | return true; 190 | }); 191 | const curr_time = new Date(); 192 | ////********///////// 193 | /////Setting up the contest//// 194 | const contest = contests.find((cont, ind) => cont.Id === room); 195 | if (!contest) { 196 | return null; 197 | } 198 | contest.Started = true; //Starting the contest 199 | contest.EndTime = curr_time.getTime() + maxDuration * 60 * 1000; 200 | contest.Problems = problems; 201 | contest.problemTags = problemTags; 202 | contest.minRating = minRating; 203 | contest.maxRating = maxRating; 204 | /////***/////////// 205 | 206 | console.log(contest); 207 | return contest; 208 | }catch(e){ 209 | return { 210 | error: "There might be an error! Please Try again !", 211 | contest: null, 212 | }; 213 | } 214 | 215 | }; 216 | 217 | //To return a list of team members in a contest 218 | const getTeamMembers = (userIds) => { 219 | try{ 220 | const newId = []; 221 | userIds.forEach((id) => { 222 | newId.push({ username: id }); 223 | }); 224 | return newId; 225 | }catch(e){ 226 | return { 227 | error: "There might be an error! Please Try again !", 228 | contest: null, 229 | }; 230 | } 231 | 232 | }; 233 | 234 | //Creating the url to fetch problems with certaing filtering 235 | const createURL = (problemTags) => { 236 | try{ 237 | const tags = problemTags.join(";"); 238 | const URL = `https://codeforces.com/api/problemset.problems?tags=${tags}`; 239 | return URL; 240 | }catch(e){ 241 | return { 242 | error: "There might be an error! Please Try again !", 243 | contest: null, 244 | }; 245 | } 246 | 247 | }; 248 | 249 | //Updating the leaderboard and problems in the contest 250 | const updateContest = async (roomId) => { 251 | try{ 252 | const contest = contests.find((con, index) => con.Id === roomId); 253 | if (!contest) { 254 | return; 255 | } 256 | 257 | const unsolvedProblems = []; 258 | 259 | contest.Problems.forEach((prob, i) => { 260 | if (!contest.userToProblem.has(prob.name)) { 261 | unsolvedProblems.push(prob.name); 262 | } 263 | }); 264 | 265 | console.log(unsolvedProblems); 266 | 267 | //Checking the problems solved by each user 268 | const promise = await contest.UsersId.map(async (user, i) => { 269 | const URL = `https://codeforces.com/api/user.status?handle=${user}&from=1&count=10`; 270 | try { 271 | const res = await axios.get(URL); 272 | unsolvedProblems.map((prob, j) => { 273 | checkIfProblemSolved(user, prob, roomId, res.data.result); 274 | }); 275 | console.log("fullfilled"); 276 | } catch (e) { 277 | console.log("rejected"); 278 | } 279 | return; 280 | }); 281 | 282 | //awaiting all the promises to return 283 | try { 284 | await Promise.all(promise); 285 | updateScores(roomId); 286 | } catch (e) { 287 | console.log("no such user"); 288 | } 289 | return contest; 290 | }catch(e){ 291 | return { 292 | error: "There might be an error! Please Try again !", 293 | contest: null, 294 | }; 295 | } 296 | }; 297 | 298 | //Validation and checking the problem solving before updating 299 | const checkIfProblemSolved = (user, unsolvedProblem, roomId, arr) => { 300 | try{ 301 | const contest = contests.find((con, index) => con.Id === roomId); 302 | 303 | if (!contest) { 304 | return; 305 | } 306 | 307 | arr.every((prob, i) => { 308 | if (check(contest, unsolvedProblem, prob)) { 309 | if ( 310 | contest.userToProblem.has(prob.problem.name) && 311 | contest.userToProblem.get(prob.problem.name).time > 312 | prob.creationTimeSeconds 313 | ) { 314 | contest.userToProblem.set(prob.problem.name, { 315 | time: prob.creationTimeSeconds, 316 | author: user, 317 | points: prob.problem.rating, 318 | }); 319 | } else if (!contest.userToProblem.has(prob.problem.name)) { 320 | contest.userToProblem.set(prob.problem.name, { 321 | time: prob.creationTimeSeconds, 322 | author: user, 323 | points: prob.problem.rating, 324 | }); 325 | } else { 326 | console.log("error"); 327 | } 328 | } 329 | }); 330 | }catch(e){ 331 | return { 332 | error: "There might be an error! Please Try again !", 333 | contest: null, 334 | }; 335 | } 336 | 337 | }; 338 | 339 | //Updating the scores for the leader board 340 | const updateScores = (roomId) => { 341 | try{ 342 | const contest = contests.find((con, index) => con.Id === roomId); 343 | 344 | if (!contest) { 345 | return; 346 | } 347 | 348 | const score = new Map(); 349 | contest.userToProblem.forEach((values, keys) => { 350 | if (score.has(values.author)) { 351 | const prevScore = score.get(values.author).points; 352 | score.set(values.author, { points: prevScore + values.points }); 353 | } else { 354 | score.set(values.author, { points: values.points }); 355 | } 356 | }); 357 | 358 | contest.Users.forEach((user, i) => { 359 | if (score.has(user.Name)) { 360 | contest.Users[i].Score = score.get(user.Name).points; 361 | } 362 | }); 363 | 364 | contest.Users.sort((a, b) => b.Score - a.Score); 365 | 366 | contest.Problems.map((problem, i) => { 367 | if (contest.userToProblem.has(problem.name)) { 368 | problem.solved = true; 369 | problem.author = contest.userToProblem.get(problem.name).author; 370 | } 371 | }); 372 | }catch(e){ 373 | return { 374 | error: "There might be an error! Please Try again !", 375 | contest: null, 376 | }; 377 | } 378 | 379 | }; 380 | 381 | //Checking the time when the problem was solved 382 | const check = (contest, unsolvedProblem, prob) => { 383 | 384 | try{ 385 | const cftime = prob.creationTimeSeconds * 1000; 386 | 387 | return ( 388 | prob.problem.name.trim().toLowerCase() == 389 | unsolvedProblem.trim().toLowerCase() && 390 | prob.verdict == "OK" && 391 | cftime >= contest.StartTime && 392 | cftime <= contest.EndTime 393 | ); 394 | }catch(e){ 395 | return { 396 | error: "There might be an error! Please Try again !", 397 | contest: null, 398 | }; 399 | } 400 | 401 | }; 402 | 403 | //Shuffling the problems array to pick random problems 404 | function shuffle(array) { 405 | for (var i = array.length - 1; i > 0; i--) { 406 | var j = Math.floor(Math.random() * (i + 1)); 407 | var temp = array[i]; 408 | array[i] = array[j]; 409 | array[j] = temp; 410 | } 411 | } 412 | 413 | //Return the contest with specific roomId 414 | const getContest = (roomId) => { 415 | return contests.find((con, index) => con.Id === roomId); 416 | }; 417 | 418 | const getContestLength = () => { 419 | return contests.length; 420 | }; 421 | 422 | 423 | //Removes the contest if exceeds the 24hrs of time 424 | const deleteContests = () => { 425 | const curr_time = new Date().getTime(); 426 | const deleteBeforeTime = curr_time - 24 * 60 * 60 * 1000; 427 | let temp = []; 428 | for (let i = 0; i < contests.length; i++) { 429 | if (contest[i].StartTime > deleteBeforeTime) temp.push(contests[i]); 430 | } 431 | contests = temp; 432 | }; 433 | 434 | module.exports = { 435 | checkContest, 436 | removeContestUser, 437 | startContest, 438 | getTeamMembers, 439 | createURL, 440 | updateContest, 441 | getContest, 442 | getContestLength, 443 | deleteContests, 444 | }; 445 | --------------------------------------------------------------------------------