├── .babelrc ├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── pull_request_template.md ├── .gitignore ├── LICENSE ├── README.md ├── client ├── app │ ├── Utils │ │ ├── api.js │ │ ├── isEmpty.js │ │ └── storage.js │ ├── actions │ │ ├── authActions.js │ │ └── types.js │ ├── components │ │ ├── Admin │ │ │ └── SignupForm.js │ │ ├── App │ │ │ ├── App.js │ │ │ ├── NotFound.js │ │ │ └── index.js │ │ ├── Footer │ │ │ ├── Footer.js │ │ │ └── index.js │ │ ├── Header │ │ │ ├── Header.js │ │ │ └── index.js │ │ ├── Home │ │ │ ├── Home.js │ │ │ └── index.js │ │ ├── Layout │ │ │ ├── ChangePassword.js │ │ │ ├── ForgotPassword.js │ │ │ ├── Landing.js │ │ │ └── Navbar.js │ │ ├── Login │ │ │ └── Login.js │ │ ├── Pages │ │ │ ├── Assignments │ │ │ │ ├── AssignmentCard.js │ │ │ │ ├── Assignments.js │ │ │ │ ├── downloadFile.js │ │ │ │ ├── index.js │ │ │ │ ├── submissionsCard.js │ │ │ │ ├── viewAssignment.js │ │ │ │ ├── viewSubmissions.js │ │ │ │ └── zipFiles.js │ │ │ ├── Contests.js │ │ │ ├── Contribute.js │ │ │ ├── Courses │ │ │ │ ├── AddAssignment.js │ │ │ │ ├── AnchorForm.js │ │ │ │ ├── CourseCard.js │ │ │ │ ├── Courses.js │ │ │ │ └── index.js │ │ │ └── Profile │ │ │ │ ├── MutableBox.js │ │ │ │ ├── PasswordBox.js │ │ │ │ ├── Profile.js │ │ │ │ ├── PublicProfile.js │ │ │ │ ├── StaticBox.js │ │ │ │ ├── UpdateHandle.js │ │ │ │ └── index.js │ │ └── common │ │ │ ├── Loading.js │ │ │ └── PrivateRoute.js │ ├── index.js │ ├── reducers │ │ ├── authReducer.js │ │ ├── errorReducer.js │ │ └── index.js │ ├── store │ │ ├── index.js │ │ └── store.js │ └── styles │ │ ├── styles.scss │ │ └── vendor │ │ └── normalize.css └── public │ ├── assets │ └── img │ │ ├── background.png │ │ └── logo.png │ └── index.html ├── config ├── config.example.js ├── helpers.js ├── webpack.common.js ├── webpack.dev.js └── webpack.prod.js ├── misc ├── README.md └── react-table.css ├── package.json ├── postcss.config.js ├── server.js ├── server ├── middleware │ ├── Token.js │ └── fileStorage.js ├── models │ ├── Assignments │ │ └── Course.js │ ├── Files.js │ ├── Group.js │ ├── Template.js │ ├── User.js │ ├── UserSession.js │ ├── assignments │ │ ├── Assignment.js │ │ └── Course.js │ └── contests │ │ ├── Contender.js │ │ └── Contest.js ├── routes │ ├── api │ │ ├── accountManagement.js │ │ ├── admin.js │ │ ├── assignments.js │ │ └── contests.js │ └── index.js ├── sendEmail │ ├── client_secret.json │ ├── forgotPassword.txt │ ├── mainMail.py │ ├── merge.py │ └── run.sh └── server.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", { 4 | "targets": { 5 | "browsers": ["last 2 versions"] 6 | }, 7 | "debug": true 8 | }], 9 | "@babel/preset-react" 10 | ], 11 | "plugins": [ 12 | "@babel/plugin-transform-runtime" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | 7 | [*.{js,css,scss,html}] 8 | charset = utf-8 9 | indent_style = space 10 | indent_size = 2 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Your environment (please complete the following information):** 27 | - OS: [e.g. linux, osx] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | - Version of node [e.g. v10.16.0] 31 | 32 | 33 | 34 | 35 | **Additional context** 36 | Add any other context about the problem here. 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | **Description of PR** 2 | 3 | A brief description of what changes your PR introduces. The better this description, the quicker we can review, exchange feedback, and merge! 4 | 5 | **Relevant Issues** 6 | Fixes #... 7 | 8 | **Checklist** 9 | 10 | - [ ] Compiles and passes lint tests 11 | - [ ] Properly formatted 12 | - [ ] Tested on desktop 13 | 14 | **Screenshots** 15 | 16 | Add relevant screenshots here 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | dist/ 3 | 4 | # Config file 5 | config/config.js 6 | 7 | # Node stuff 8 | node_modules 9 | .npm 10 | .node_repl_history 11 | .lock-wscript 12 | logs 13 | *.log 14 | npm-debug.log* 15 | 16 | # OS stuff 17 | Desktop.ini 18 | ehthumbs.db 19 | Thumbs.db 20 | $RECYCLE.BIN/ 21 | ._* 22 | .DS_Store 23 | .Spotlight-V100 24 | .Trashes 25 | .vscode/ 26 | 27 | # node files 28 | package-lock.json 29 | 30 | # ssl files 31 | server/sslcert/ 32 | 33 | # email extra files 34 | server/sendEmail/__pycache__/ 35 | server/sendEmail/emails.csv -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Eugene Cheung 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Data Analysis Project 2 | A web application built using MERN (MongoDB, Express.js, React.js, Node.js) full-stack framework. This project is developed for the students and professors of the CS&E department, PES University and is developed by the members of *The Alcoding Club*, PES University. 3 | The web app enables student to view their ranking among other students in global competitive coding competitions, helps them build a public profile, submit course related assignments and view their submissions. 4 | 5 | ## Requirements 6 | 7 | - [Node.js](https://nodejs.org/en/) 6+ 8 | - [MongoDB](https://docs.mongodb.com/manual/installation/) 9 | 10 | ```shell 11 | npm install 12 | ``` 13 | 14 | 15 | ## Instructions 16 | 17 | Production mode: 18 | 19 | ```shell 20 | npm start 21 | ``` 22 | 23 | Development (Webpack dev server) mode: 24 | 25 | ```shell 26 | npm run start:dev 27 | ``` 28 | 29 | ### Note 30 | 31 | 1. Make sure to add a `config.js` file in the `config` folder. See the `config.example.js` under `config/` directory for more details. 32 | 2. Generate and add ssl files in the `server/` directory under a folder named `sslcert`. To genrate certificates, navigate to the `server/sslcert/` directory and execute the following command. 33 | ```shell 34 | openssl req -x509 -out server.crt -keyout server.key \ 35 | -newkey rsa:2048 -nodes -sha256 \ 36 | -subj '/CN=localhost' -extensions EXT -config <( \ 37 | printf "[dn]\nCN=localhost\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:localhost\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth") 38 | ``` 39 | This should generate two files, `server.key` and `server.crt`. 40 | 41 | ### Steps to create the first user (an admin user) 42 | 1. Replace line 14 in `server/routes/api/admin.js` by the following 43 | ``` 44 | app.post('/api/admin/signup', function (req, res) { 45 | ``` 46 | 2. Make a `POST` request using any REST API client to `http://localhost:8080/api/admin/signup` with the body as `{ "usn": "admin", "firstName": "YourName" }` 47 | 48 | 3. Using Mongo Compass, change the role of the created user to `admin`. 49 | 4. Undo changes to `server/routes/api/admin.js`. 50 | 5. Run the server. 51 | 6. Log in using admin credentials. ( username = "ADMIN", password = "ADMIN" ) 52 | 53 | ### Steps to add new users 54 | 1. Using an `admin` account, access `localhost:8080/admin` page. Upload a csv file containing new users in the format "firstName, email, usn". 55 | 2. Default password is USN (in uppercase) for all users. 56 | -------------------------------------------------------------------------------- /client/app/Utils/api.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { logoutUser } from './../actions/authActions'; 3 | 4 | const _API_CALL = (apiPath, method, body, token) => { 5 | switch (method) { 6 | case "POST": 7 | case "post": 8 | return axios.post(apiPath, body, { 9 | headers: { 10 | 'x-access-token': token, 11 | 'Content-Type': 'application/json' 12 | } 13 | }).then(function (response) { 14 | return response; 15 | }).catch((error) => { 16 | if (error.response) { 17 | if (error.response.status == 401) { 18 | // Invalid token 19 | dispatch(logoutUser()); 20 | error = { ...error, message: "User not authenticated." }; 21 | } 22 | else if (error.response.status == 500) { 23 | // Internal server error 24 | error = { ...error, message: "Server Error. Please try again after a while." }; 25 | } 26 | else { 27 | console.log(error) 28 | } 29 | } 30 | throw error; 31 | }); 32 | break; 33 | 34 | case "GET": 35 | case "get": 36 | return axios.get(apiPath, { 37 | headers: { 38 | 'x-access-token': token, 39 | 'Content-Type': 'application/json' 40 | } 41 | }) 42 | .then((response) => { 43 | return response; 44 | }) 45 | .catch((error) => { 46 | if (error.response) { 47 | if (error.response.status == 401) { 48 | // Invalid token 49 | dispatch(logoutUser()); 50 | error = { ...error, message: "User not authenticated." }; 51 | } 52 | else if (error.response.status == 500) { 53 | // Internal server error 54 | error = { ...error, message: "Server Error. Please try again after a while." }; 55 | } 56 | else { 57 | console.log(error) 58 | } 59 | } 60 | throw error; 61 | }); 62 | break; 63 | 64 | } 65 | }; 66 | 67 | export { _API_CALL }; -------------------------------------------------------------------------------- /client/app/Utils/isEmpty.js: -------------------------------------------------------------------------------- 1 | const isEmpty = value => 2 | value === undefined || 3 | value === null || 4 | (typeof value === 'object' && Object.keys(value).length === 0) || 5 | (typeof value === 'string' && value.trim().length === 0); 6 | 7 | export default isEmpty; 8 | -------------------------------------------------------------------------------- /client/app/Utils/storage.js: -------------------------------------------------------------------------------- 1 | export function getFromStorage(key) { 2 | if (!key) { 3 | return null; 4 | } 5 | try { 6 | const valueStr = localStorage.getItem(key); 7 | if (valueStr) { 8 | return JSON.parse(valueStr); 9 | } 10 | return null; 11 | } catch (err) { 12 | return null; 13 | } 14 | } 15 | export function setInStorage(key, obj) { 16 | if (!key) { 17 | console.error('Error: Key is missing'); 18 | } 19 | try { 20 | localStorage.setItem(key, JSON.stringify(obj)); 21 | } catch (err) { 22 | console.error(err); 23 | } 24 | } -------------------------------------------------------------------------------- /client/app/actions/authActions.js: -------------------------------------------------------------------------------- 1 | import { SET_CURRENT_USER, SET_DETAILS, LOGOUT_USER } from '../actions/types'; 2 | import axios from 'axios'; 3 | 4 | /* 5 | ACTION CREATORS 6 | */ 7 | // Set a logged in user 8 | export const setCurrentUser = (token, user_id) => { 9 | return { 10 | type: SET_CURRENT_USER, 11 | payload: { 12 | token, user_id 13 | } 14 | }; 15 | }; 16 | 17 | // Set name of logged in user 18 | export const setName = (name) => { 19 | return { 20 | type: SET_DETAILS, 21 | payload: { 22 | name 23 | } 24 | }; 25 | }; 26 | 27 | // Logout a user 28 | const logoutUserCreator = () => { 29 | return { 30 | type: LOGOUT_USER, 31 | payload: { 32 | success: true 33 | } 34 | }; 35 | }; 36 | 37 | 38 | /* 39 | ACTION FUNCTIONS aka THUNKS 40 | */ 41 | 42 | export const logoutUser = () => { 43 | return (dispatch, getState) => { 44 | const userID = getState().auth.user_id; 45 | const token = getState().auth.token; 46 | axios.get('/api/account/' + userID + '/logout', { 47 | headers: { 48 | 'x-access-token': token, 49 | 'Content-Type': 'application/json' 50 | }, 51 | }) 52 | .then(response => { 53 | console.log(response); 54 | }) 55 | .catch(err => { 56 | console.log(err); 57 | }) 58 | .finally(() => { 59 | console.log(userID + " logged out."); 60 | //remove data from local storage 61 | localStorage.clear(); 62 | return dispatch(logoutUserCreator()); 63 | }) 64 | } 65 | } 66 | 67 | export const getName = () => { 68 | return (dispatch, getState) => { 69 | const userID = getState().auth.user_id; 70 | const token = getState().auth.token; 71 | axios.get('/api/account/' + userID + '/details', 72 | { 73 | headers: { 74 | 'x-access-token': token, 75 | 'Content-Type': 'application/json' 76 | }, 77 | }) 78 | .then(res => { 79 | dispatch(setName(res.data.user.name)); 80 | }) 81 | .catch((error) => { 82 | if (error.response) { 83 | if (error.response.status == 401) { 84 | dispatch(logoutUser()); 85 | } 86 | } 87 | else console.log(error) 88 | }); 89 | } 90 | } 91 | 92 | export const loginUser = user => dispatch => { 93 | axios.post("/api/account/signin", user) 94 | .then((res) => { 95 | // console.log(res); 96 | 97 | if (res.data.success) { 98 | //save data into local storage 99 | localStorage.setItem('token', res.data.token); 100 | localStorage.setItem('user_id', res.data.user_id); 101 | 102 | //set current user 103 | dispatch(setCurrentUser(res.data.token, res.data.user_id)); 104 | } 105 | dispatch(getName()); 106 | }) 107 | .catch(err => { 108 | console.log(err); 109 | alert('Invalid Login.'); 110 | } 111 | ); 112 | }; 113 | -------------------------------------------------------------------------------- /client/app/actions/types.js: -------------------------------------------------------------------------------- 1 | export const SET_CURRENT_USER = 'SET_CURRENT_USER'; 2 | export const SET_DETAILS = 'SET_DETAILS'; 3 | export const GET_ERRORS = 'GET_ERRORS'; 4 | export const CLEAR_ERRORS = 'CLEAR_ERRORS'; 5 | export const LOGOUT_USER = 'LOGOUT_USER'; 6 | -------------------------------------------------------------------------------- /client/app/components/Admin/SignupForm.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Redirect, Link } from 'react-router-dom'; 3 | import axios from 'axios'; 4 | import ReactLoading from 'react-loading'; 5 | 6 | class SignupForm extends Component { 7 | constructor() { 8 | super(); 9 | this.state = { 10 | isLoading: true, 11 | group_name: "", 12 | grad_year:"", 13 | } 14 | this.handleSubmitStudents = this.handleSubmitStudents.bind(this); 15 | this.handleSubmitContenders = this.handleSubmitContenders.bind(this); 16 | this.handleSubmitGroup = this.handleSubmitGroup.bind(this); 17 | this.handleDownload = this.handleDownload.bind(this); 18 | this.fileInput = React.createRef(); 19 | this.fileInput_contest = React.createRef(); 20 | this.fileInput_group = React.createRef(); 21 | this.changeGroupName = this.changeGroupName.bind(this); 22 | this.changeGradYear = this.changeGradYear.bind(this); 23 | } 24 | 25 | componentDidMount() { 26 | var token = localStorage.getItem('token'); 27 | var userID = localStorage.getItem('user_id'); 28 | var self = this; 29 | var apiPath = '/api/account/' + userID + '/details' 30 | axios.get(apiPath, { 31 | headers: { 32 | 'x-access-token': token, 33 | 'Content-Type': 'application/json' 34 | } 35 | }) 36 | .then(function (response) { 37 | self.setState({ 38 | isLoading: false 39 | }); 40 | var data = response.data; 41 | if (data.user.role != "admin") { 42 | self.props.history.push('/'); 43 | } 44 | }) 45 | .catch(function (error) { 46 | console.log(error); 47 | self.props.history.push('/'); 48 | }); 49 | } 50 | 51 | handleSubmitStudents(event) { 52 | event.preventDefault(); 53 | var fileObj = this.fileInput.current.files[0]; 54 | var token = 'Bearer ' + localStorage.getItem('token'); 55 | var configSignup = { 56 | headers: { 57 | 'Content-Type': 'application/x-www-form-urlencoded', 58 | 'Authorization': token 59 | } 60 | }; 61 | var signUpUrl = '/api/admin/signup'; 62 | const NO_OF_MANDATORY_FIELDS = 3; //NUMBER OF FIELDS MANDATORY FOR SIGNUP. 63 | var reader = new FileReader(); 64 | reader.onload = function (file) { 65 | var data = file.target.result.split('\n'); 66 | var row, invalid, attributes, count; 67 | for (var row_count = 0; row_count < data.length; row_count++) { 68 | row = data[row_count]; 69 | invalid = 0; 70 | attributes = row.split(','); 71 | for (count = 0; count < NO_OF_MANDATORY_FIELDS; count++) { 72 | if (attributes[count] == "") { invalid = 1; } 73 | };//to check cases where there are blank fields for each user 74 | if (!invalid) { 75 | var body = "firstName=" + attributes[0]; 76 | body += '&email=' + attributes[1]; 77 | body += '&usn=' + attributes[2]; 78 | 79 | axios.post(signUpUrl, body, configSignup) 80 | .then(function (response) { 81 | // console.log(response.data); 82 | }) 83 | .catch(function (err) { 84 | console.log(err); 85 | }) 86 | } 87 | else { 88 | console.log("error at user: " + attributes); 89 | } 90 | } 91 | } 92 | if (fileObj) { 93 | reader.readAsText(fileObj, "UTF-8"); 94 | } 95 | else { console.log('Please Upload a file!'); } 96 | } 97 | 98 | handleSubmitContenders(event) { 99 | event.preventDefault(); 100 | var fileObj = this.fileInput_contest.current.files[0]; 101 | var token = 'Bearer ' + localStorage.getItem('token'); 102 | var configSignup = { 103 | headers: { 104 | 'Content-Type': 'application/x-www-form-urlencoded', 105 | 'Authorization': token 106 | } 107 | }; 108 | var signUpUrl = '/api/contests/updateContenders'; 109 | var reader = new FileReader(); 110 | reader.onload = function (file) { 111 | var data = JSON.parse(file.target.result); 112 | for (var key in data) { 113 | // console.log(key + " -> " + data[key]); 114 | var body = "usn=" + key; 115 | body += "&name=" + data[key].name; 116 | body += "&email=" + data[key].email; 117 | body += "&rating=" + data[key].rating; 118 | body += "&volatility=" + data[key].volatility; 119 | body += "×Played=" + data[key].timesPlayed; 120 | body += "&lastFive=" + data[key].lastFive; 121 | body += "&best=" + data[key].best; 122 | body += "&codejam=" + data[key].codejam; 123 | body += "&hackerearth=" + data[key].hackerearth; 124 | 125 | axios.post(signUpUrl, body, configSignup) 126 | .then(function (response) { 127 | console.log(response.data); 128 | }) 129 | .catch(function (err) { 130 | console.log(err); 131 | }) 132 | } 133 | } 134 | 135 | if (fileObj) { 136 | reader.readAsText(fileObj, "UTF-8"); 137 | } 138 | else { console.log('Please Upload a file!..'); } 139 | } 140 | 141 | handleSubmitGroup(e){ 142 | e.preventDefault(); 143 | var fileObj = this.fileInput_group.current.files[0]; 144 | var token = 'Bearer ' + localStorage.getItem('token'); 145 | var configSignup = { 146 | headers: { 147 | 'Content-Type': 'application/x-www-form-urlencoded', 148 | 'Authorization': token 149 | } 150 | }; 151 | var createGroupUrl = '/api/admin/createGroup'; 152 | var reader = new FileReader(); 153 | var body = 'name='+this.state.group_name+'&graduating='+parseInt(this.state.grad_year); 154 | reader.onload = function (file) { 155 | var data = file.target.result.split('\n'); 156 | for (var row_count = 0; row_count < data.length; row_count++) { 157 | var row = data[row_count].split(',')[0]; 158 | var obj = body+'&usn='+row 159 | console.log(obj); 160 | axios.post(createGroupUrl, obj, configSignup) 161 | .then(function(response) { 162 | console.log(response.data); 163 | }) 164 | .catch(function (err) { 165 | console.log(err); 166 | }) 167 | } 168 | } 169 | 170 | if (fileObj) { 171 | reader.readAsText(fileObj, "UTF-8"); 172 | } 173 | else { console.log('Please Upload a file!..'); } 174 | } 175 | 176 | changeGroupName(e){ 177 | this.setState({ 178 | group_name: e.target.value 179 | }) 180 | } 181 | 182 | changeGradYear(e){ 183 | this.setState({ 184 | grad_year: e.target.value 185 | }) 186 | } 187 | 188 | handleDownload(event) { 189 | event.preventDefault(); 190 | 191 | } 192 | 193 | render() { 194 | const updatedHandlesWtoken = "/api/contests/updatedHandles?token=" + localStorage.getItem('token'); 195 | if (this.state.isLoading) 196 | return ; 197 | else 198 | return ( 199 |
200 |

Upload Student Details:

201 |
202 | Please upload a .csv file 203 | 204 |
205 | 206 |
207 |
208 |

Upload/Download Contender Details:

209 |
210 | Please upload a .json file 211 | 212 |
213 |   214 | {/* */} 215 | Download 216 |
217 |
218 |

Create New Group:

219 |
220 | Name of Group: 221 | Year of Graduation: 222 | Please upload a csv file of student USN's in group 223 | 224 |
225 |   226 |
227 |
228 | ); 229 | } 230 | } 231 | 232 | export default SignupForm; 233 | -------------------------------------------------------------------------------- /client/app/components/App/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import Header from '../Header'; 4 | import Footer from '../Footer'; 5 | 6 | const App = ({ children }) => ( 7 |
8 |
9 | 10 |
18 | {children} 19 |
20 | 21 |
22 |
23 | ); 24 | 25 | export default App; -------------------------------------------------------------------------------- /client/app/components/App/NotFound.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link, Redirect } from 'react-router-dom'; 3 | 4 | const NotFound = ({ location }) => ( 5 |
6 |
7 |
8 |

Page not found

9 |

The requested URL {location.pathname} was not found on this server. 10 |

11 | Go to Homepage 12 |
13 |
14 |
15 | ); 16 | 17 | export default NotFound; 18 | 19 | 20 | -------------------------------------------------------------------------------- /client/app/components/App/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './App'; -------------------------------------------------------------------------------- /client/app/components/Footer/Footer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import { 4 | Container 5 | } from 'reactstrap'; 6 | 7 | const Footer = () => ( 8 | 19 | ); 20 | 21 | export default Footer; 22 | 23 | // OLD 24 | // {/*
25 | //
26 | //
27 | //
We'd love your help! Contribute to this project on Github.
28 | //

Copyright © The Alcoding Club, CS&E, PES University, Bengaluru, India.

29 | //
30 | //
*/} -------------------------------------------------------------------------------- /client/app/components/Footer/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './Footer'; -------------------------------------------------------------------------------- /client/app/components/Header/Header.js: -------------------------------------------------------------------------------- 1 | import React,{ Component } from 'react'; 2 | import Navbar from '../Layout/Navbar'; 3 | 4 | class Header extends Component { 5 | constructor() { 6 | super(); 7 | } 8 | render(){ 9 | return( 10 |
11 | 12 |
13 | ); 14 | } 15 | } 16 | export default Header; 17 | -------------------------------------------------------------------------------- /client/app/components/Header/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './Header'; -------------------------------------------------------------------------------- /client/app/components/Home/Home.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import axios from 'axios'; 3 | import ReactTable from "react-table"; 4 | import 'react-table/react-table.css'; 5 | 6 | class Home extends Component { 7 | constructor() { 8 | super(); 9 | this.state = { 10 | globalRankList: [], 11 | loading: true 12 | }; 13 | } 14 | 15 | componentDidMount() { 16 | var self = this; 17 | var token = localStorage.getItem('token'); 18 | var userID = localStorage.getItem('user_id'); 19 | 20 | var apiPath = '/api/contests/globalRankList'; 21 | axios.get(apiPath, { 22 | headers: { 23 | 'x-access-token': token, 24 | 'Content-Type': 'application/json' 25 | } 26 | }) 27 | .then(function (response) { 28 | if (!response.data.success) { 29 | console.log("Error: " + response.data); 30 | return; 31 | } 32 | var data = response.data.globalRankList.userContenderDetails; 33 | data.sort(function (a, b) { 34 | return b.rating - a.rating; 35 | }); 36 | self.setState({ 37 | globalRankList: data, 38 | loading: false 39 | }); 40 | 41 | }) 42 | .catch(function (error) { 43 | self.setState({ 44 | loading: false 45 | }) 46 | console.log('Error: ', error); 47 | }); 48 | } 49 | 50 | render() { 51 | 52 | const data = this.state.globalRankList; 53 | 54 | const columns = 55 | [ 56 | { 57 | Header: "Rank", 58 | id: "row", 59 | maxWidth: 65, 60 | filterable: false, 61 | Cell: (row) => { 62 | return
{row.index + 1}
; 63 | } 64 | }, 65 | { 66 | Header: "Name", 67 | accessor: "name" 68 | }, 69 | { 70 | Header: "USN", 71 | accessor: "usn", 72 | maxWidth: 200, 73 | }, 74 | { 75 | Header: "Contests", 76 | accessor: "timesPlayed", 77 | maxWidth: 100, 78 | }, 79 | { 80 | Header: "Rating", 81 | accessor: "rating", 82 | maxWidth: 150, 83 | }, 84 | { 85 | Header: "Best", 86 | accessor: "best", 87 | maxWidth: 150, 88 | } 89 | ] 90 | 91 | const staticText = { 92 | aboutUs: "ALCODING Club - a club of ALgorithms and CODING enthusiasts in the department of Computer Science & Engineering, PES University. It is founded and led by Prof. Channa Bankapur to encourage and keep up the coding culture in the campus.", 93 | latestNews: "1 October 2018: Release of the Alcoding Club Platform which will enable students of the department to view Global Rankings, upcoming contests and any messages.", 94 | announcements: "The ACM International Collegiate Programming Contest (ACM ICPC) is the largest collegiate programming contest being organized all over the world every year. The ACM ICPC is an activity of the ACM that provides college students with an opportunity to demonstrate and sharpen their problem-solving and computing skills. The contest is considered as the \"Olympiad of Computer Programming\". For more information about ACM ICPC, visit https://icpc.baylor.edu" 95 | } 96 | 97 | return ( 98 |
99 |
100 |
101 |

About Us

102 |

{staticText.aboutUs}

103 |
104 |
105 |

Latest News

106 |

{staticText.latestNews}

107 |
108 |
109 |

Announcements

110 |

{staticText.announcements}

111 |
112 |
113 |
114 |
Global Rank List
115 |
116 | 131 |
132 |
133 |
134 | ); 135 | } 136 | } 137 | 138 | export default Home; 139 | -------------------------------------------------------------------------------- /client/app/components/Home/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './Home'; -------------------------------------------------------------------------------- /client/app/components/Layout/ChangePassword.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import axios from 'axios'; 4 | import { Link, Redirect } from 'react-router-dom'; 5 | 6 | //import StaticBox from './StaticBox.js'; 7 | 8 | import PasswordBox from '../Pages/Profile/PasswordBox'; 9 | 10 | class ChangePassword extends Component { 11 | constructor() { 12 | super(); 13 | this.state = { 14 | newPassword: "", 15 | confirmNewPassword: "", 16 | }; 17 | 18 | this.changeNewPassword = this.changeNewPassword.bind(this); 19 | this.changeConfirmNewPassword = this.changeConfirmNewPassword.bind(this); 20 | this.confirmPasswordChange = this.confirmPasswordChange.bind(this); 21 | } 22 | 23 | changeNewPassword(event) { 24 | event.preventDefault(); 25 | this.setState({ 26 | newPassword: event.target.value, 27 | }); 28 | 29 | } 30 | 31 | changeConfirmNewPassword(event) { 32 | event.preventDefault(); 33 | this.setState({ 34 | confirmNewPassword: event.target.value, 35 | }); 36 | 37 | } 38 | 39 | confirmPasswordChange() { 40 | const { match: { params } } = this.props; 41 | var user_ID = params.userID; 42 | var token = params.token; 43 | 44 | var body = { 45 | userID: user_ID, 46 | newPassword: this.state.newPassword 47 | } 48 | //api/account/:userID/newPassword 49 | if (this.state.newPassword != this.state.confirmNewPassword) { 50 | alert("Passwords do not match."); 51 | } 52 | 53 | else { 54 | // api call needs to be updated 55 | axios.post(`/api/account/${user_ID}/newPassword`, body, { 56 | headers: { 57 | 'x-access-token': token, 58 | 'Content-Type': 'application/json' 59 | } 60 | }).then(res => { 61 | alert(res.data.message); 62 | this.props.history.push('/'); 63 | }) 64 | .catch(err => { 65 | console.log(err); 66 | if (err.response) { 67 | if (err.response.status == 401) { 68 | alert("Token expired. Please request for a password change again."); 69 | window.location.href = '/'; 70 | } 71 | } 72 | else 73 | alert("Error changing the password. Please try after a while."); 74 | }) 75 | } 76 | } 77 | 78 | 79 | render() { 80 | return ( 81 |
82 | 83 | 84 | 126 |
127 | ); 128 | } 129 | } 130 | 131 | export default ChangePassword; 132 | -------------------------------------------------------------------------------- /client/app/components/Layout/ForgotPassword.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import axios from "axios"; 3 | import { Link, Redirect } from 'react-router-dom'; 4 | import ReactLoading from '../common/Loading'; 5 | import { ToastContainer, ToastStore } from 'react-toasts'; 6 | 7 | export default class ForgotPassword extends Component { 8 | constructor(props) { 9 | super(props); 10 | this.state = { 11 | isLoading: false, 12 | signInUsn: "", 13 | isLoading: false 14 | }; 15 | this.onTextboxChangeSignInUsn = this.onTextboxChangeSignInUsn.bind(this); 16 | this.sendEmail = this.sendEmail.bind(this); 17 | 18 | } 19 | 20 | 21 | onTextboxChangeSignInUsn(event) { 22 | event.preventDefault(); 23 | this.setState({ 24 | signInUsn: event.target.value, 25 | }); 26 | 27 | } 28 | 29 | sendEmail() { 30 | if (!this.state.signInUsn) { 31 | ToastStore.error("USN cannot be empty.") 32 | return; 33 | } 34 | 35 | this.setState({ 36 | isLoading: true 37 | }); 38 | // /api/account/forgotPassword 39 | var config = { 40 | headers: { 41 | 'Content-Type': 'application/json', 42 | } 43 | } 44 | var data = { USN: this.state.signInUsn } 45 | axios.post("/api/account/forgotPassword", data, config) 46 | .then(res => { 47 | // console.log(res); 48 | this.setState({ isLoading: false }); 49 | // alert(res.data.message); 50 | ToastStore.success(res.data.message); 51 | setTimeout(()=>{ 52 | this.setState({ redirect: true }) 53 | }, 2000); 54 | }) 55 | .catch(err => { 56 | this.setState({ isLoading: false }); 57 | if (err.response) { 58 | if (err.response.status == 404) { 59 | // alert("User not found."); 60 | ToastStore.error('User not found.'); 61 | } 62 | } 63 | else { 64 | // alert("Unable to send email. Please try again later."); 65 | ToastStore.error("Unable to send email. Please try again later.") 66 | } 67 | }) 68 | } 69 | 70 | renderRedirect() { 71 | if (this.state.redirect) { 72 | return 73 | } 74 | } 75 | 76 | render() { 77 | return ( 78 |
79 | {this.renderRedirect()} 80 |

Change Password

81 |
82 | 83 | 91 |
92 |
93 | {!this.state.isLoading ? : } 94 |
95 | 96 |
97 | ) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /client/app/components/Layout/Landing.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { connect } from 'react-redux'; 4 | 5 | class Landing extends Component { 6 | constructor(props) { 7 | super(props); 8 | } 9 | render() { 10 | 11 | return ( 12 | 13 |
14 |
15 |
16 |

News

17 |

18 | Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. 19 |

20 |











21 |
22 |
23 | 24 |
25 |
26 | ); 27 | } 28 | } 29 | 30 | 31 | export default Landing; -------------------------------------------------------------------------------- /client/app/components/Layout/Navbar.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link, Redirect } from 'react-router-dom'; 3 | import PropTypes from 'prop-types'; 4 | import { connect } from 'react-redux'; 5 | import { loginUser, logoutUser } from '../../actions/authActions'; 6 | import { 7 | Button, 8 | Form, 9 | FormGroup, 10 | Label, 11 | Input, 12 | Collapse, 13 | Navbar, 14 | NavbarToggler, 15 | NavbarBrand, 16 | Nav, 17 | NavItem, 18 | NavLink, 19 | UncontrolledDropdown, 20 | DropdownToggle, 21 | DropdownMenu, 22 | DropdownItem, 23 | Container 24 | } from 'reactstrap'; 25 | 26 | class NavbarClass extends Component { 27 | constructor() { 28 | super(); 29 | this.state = { 30 | signInUsn: "", 31 | signInpassword: "", 32 | loginShow: true, 33 | navbarIsOpen: false 34 | }; 35 | 36 | this.toggle = this.toggle.bind(this); 37 | this.onSignIn = this.onSignIn.bind(this); 38 | this.onTextboxChangeSignInUsn = this.onTextboxChangeSignInUsn.bind(this); 39 | this.onTextboxChangeSignInPassword = this.onTextboxChangeSignInPassword.bind(this); 40 | }; 41 | 42 | 43 | componentDidMount() { 44 | // 45 | } 46 | 47 | onTextboxChangeSignInPassword(event) { 48 | event.preventDefault(); 49 | this.setState({ 50 | signInPassword: event.target.value, 51 | }); 52 | 53 | } 54 | 55 | onTextboxChangeSignInUsn(event) { 56 | event.preventDefault(); 57 | this.setState({ 58 | signInUsn: event.target.value, 59 | }); 60 | 61 | } 62 | 63 | onSignIn(event) { 64 | event.preventDefault(); 65 | const user = { 66 | usn: this.state.signInUsn, 67 | password: this.state.signInPassword 68 | }; 69 | 70 | this.props.loginUser(user); 71 | } 72 | 73 | onLogoutClick(event) { 74 | event.preventDefault(); 75 | // this.forceUpdate(); 76 | this.props.logoutUser(); 77 | } 78 | 79 | reload() { 80 | // this.forceUpdate(); 81 | // window.location.reload(); 82 | } 83 | 84 | toggle() { 85 | this.setState({ 86 | navbarIsOpen: !this.state.navbarIsOpen 87 | }); 88 | } 89 | 90 | render() { 91 | var isAuthenticated = this.props.auth.isAuthenticated; 92 | var displayName = (this.props.auth.name && this.props.auth.name.firstName) || ""; 93 | 94 | const authLinks = ( 95 | 96 | 107 |
108 | 132 |
133 | ); 134 | 135 | const guestLinks = ( 136 | 137 | 166 | 167 | ); 168 | 169 | return ( 170 | 171 | 172 | The Alcoding Club 173 | 174 | {isAuthenticated ? authLinks : guestLinks} 175 | 176 | 177 | ); 178 | } 179 | } 180 | 181 | NavbarClass.propTypes = { 182 | auth: PropTypes.object.isRequired, 183 | logoutUser: PropTypes.func.isRequired, 184 | loginUser: PropTypes.func.isRequired, 185 | }; 186 | 187 | const mapStateToProps = state => ({ 188 | auth: state.auth, 189 | }); 190 | 191 | export default connect(mapStateToProps, { loginUser, logoutUser })(NavbarClass); 192 | 193 | -------------------------------------------------------------------------------- /client/app/components/Login/Login.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Redirect } from 'react-router-dom'; 3 | import PropTypes from 'prop-types'; 4 | import { connect } from 'react-redux'; 5 | import { loginUser } from '../../actions/authActions'; 6 | class Login extends Component { 7 | constructor() { 8 | super(); 9 | this.state = { 10 | signInEmail: "", 11 | signInpassword: "", 12 | errors: {}, 13 | }; 14 | 15 | this.onSignIn = this.onSignIn.bind(this); 16 | this.onTextboxChangeSignInEmail = this.onTextboxChangeSignInEmail.bind(this); 17 | this.onTextboxChangeSignInPassword = this.onTextboxChangeSignInPassword.bind(this); 18 | }; 19 | 20 | componentDidMount() { 21 | if (this.props.auth.isAuthenticated) { 22 | this.props.history.push('/landing'); 23 | } 24 | } 25 | 26 | componentWillReceiveProps(nextProps) { 27 | if (nextProps.auth.isAuthenticated) { 28 | this.props.history.push('/landing'); 29 | } 30 | 31 | if (nextProps.errors) { 32 | this.setState({ errors: nextProps.errors }); 33 | } 34 | } 35 | 36 | onTextboxChangeSignInPassword(event) { 37 | event.preventDefault(); 38 | this.setState({ 39 | signInPassword: event.target.value, 40 | }); 41 | 42 | } 43 | 44 | onTextboxChangeSignInEmail(event) { 45 | event.preventDefault(); 46 | this.setState({ 47 | signInEmail: event.target.value, 48 | }); 49 | 50 | } 51 | 52 | onSignIn(event) { 53 | event.preventDefault(); 54 | const user = { 55 | email: this.state.signInEmail, 56 | password: this.state.signInPassword 57 | }; 58 | 59 | this.props.loginUser(user); 60 | } 61 | 62 | render() { 63 | const { 64 | signInEmail, 65 | signInPassword, 66 | errors 67 | } = this.state; 68 | 69 | return ( 70 | 71 |
72 |
73 |
74 |

Log In

75 |
76 | 85 |
86 |
87 | 96 |
97 | 98 | 99 |
100 | 101 |
102 |
103 | 104 |
105 |
106 | ) 107 | } 108 | } 109 | 110 | Login.propTypes = { 111 | loginUser: PropTypes.func.isRequired, 112 | auth: PropTypes.object.isRequired, 113 | errors: PropTypes.object.isRequired 114 | 115 | }; 116 | 117 | const mapStateToProps = state => ({ 118 | auth: state.auth, 119 | errors: state.errors 120 | 121 | }); 122 | 123 | export default connect(mapStateToProps, { loginUser })(Login); 124 | -------------------------------------------------------------------------------- /client/app/components/Pages/Assignments/AssignmentCard.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import axios from 'axios'; 3 | import { Link, Redirect } from 'react-router-dom'; 4 | import viewSubmissions from './viewSubmissions'; 5 | 6 | class AssignmentCard extends Component { 7 | constructor(props) { 8 | super(props); 9 | this.state = ({ 10 | file: null, 11 | showUpload: true 12 | }) 13 | this.onSubmit = this.onSubmit.bind(this); 14 | this.onChange = this.onChange.bind(this); 15 | this.fileInput = React.createRef(); 16 | } 17 | 18 | componentDidMount() { 19 | var userID = localStorage.getItem('user_id'); 20 | var success = 0; 21 | if (this.props.submissions.length) { 22 | for (var i = 0; i < this.props.submissions.length; i++) { 23 | var submission = this.props.submissions[i]; 24 | if (submission.user == userID) { 25 | this.setState({ 26 | showUpload: false 27 | }) 28 | } 29 | } 30 | } 31 | 32 | else { 33 | this.setState({ 34 | showUpload: true 35 | }) 36 | } 37 | } 38 | 39 | onChange(e) { 40 | this.setState({ 41 | file: e.target.files[0] 42 | }); 43 | } 44 | 45 | onSubmit(event) { 46 | event.preventDefault(); 47 | var self = this 48 | var userID = localStorage.getItem('user_id'); 49 | var token = 'Bearer ' + localStorage.getItem('token'); 50 | var assignmentID = this.props.assignmentID; 51 | var inputData = new FormData(); 52 | inputData.append("inputFile", this.state.file); 53 | var config = { 54 | headers: { 55 | 'Content-Type': 'multipart/form-data', 56 | 'Authorization': token 57 | } 58 | } 59 | 60 | var apiPath = 'api/assignments/' + userID + '/' + assignmentID + '/upload' 61 | axios.post(apiPath, inputData, config) 62 | .then(res => { 63 | console.log(res.data) 64 | if (res.data.success) { 65 | self.setState({ 66 | showUpload: false 67 | }) 68 | } 69 | else { 70 | alert('Assignment failed to upload!') 71 | } 72 | }) 73 | 74 | } 75 | 76 | render() { 77 | const toUpload = ( 78 |
79 |
80 | {/* 81 | */} 82 |
83 |
84 |
85 | 86 |
87 |
88 | ); 89 | let content; 90 | const profContent = ( 91 |
92 |
93 |

{this.props.uniqueID}: {this.props.name}

94 |
95 | Description: {this.props.details}
96 | Type: {this.props.type}
97 | Due Date: {this.props.dueDate}
98 | Maximum Marks: {this.props.maxMarks}
99 | Resource URL: {this.props.resourceUrl}

100 | View Assignment 112 | View Submissions 118 | 119 |
120 |
121 |
122 |
123 | ); 124 | const studContent = ( 125 |
126 |
127 |

{this.props.uniqueID}: {this.props.name}

128 |
129 | Description: {this.props.details}
130 | Type: {this.props.type}
131 | Due Date: {this.props.dueDate}
132 | Maximum Marks: {this.props.maxMarks}
133 | Resource URL: {this.props.resourceUrl}

134 | {this.state.showUpload ? toUpload :
Assignment Submitted!
} 135 |
136 |
137 |
138 |
139 | ); 140 | 141 | if (this.props.role == "prof") { 142 | content = profContent; 143 | } 144 | else { 145 | content = studContent; 146 | } 147 | 148 | return ( 149 |
{content}
150 | 151 | ) 152 | } 153 | } 154 | 155 | export default AssignmentCard; 156 | -------------------------------------------------------------------------------- /client/app/components/Pages/Assignments/Assignments.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link, Redirect } from 'react-router-dom'; 3 | import axios from 'axios'; 4 | import AssignmentCard from './AssignmentCard'; 5 | import ReactLoading from './../../common/Loading'; 6 | 7 | class Assignments extends Component { 8 | constructor(props) { 9 | super(props); 10 | this.state = { 11 | isLoading: true, 12 | courses: [], 13 | role: "student", 14 | assignments: [] 15 | }; 16 | } 17 | componentDidMount() { 18 | var self = this; 19 | var token = localStorage.getItem('token'); 20 | var userID = localStorage.getItem('user_id'); 21 | 22 | var apiPath = '/api/account/' + userID + '/details' 23 | axios.get(apiPath, { 24 | headers: { 25 | 'x-access-token': token, 26 | 'Content-Type': 'application/json' 27 | } 28 | }) 29 | .then(function (response) { 30 | if (!response.data.success) { 31 | // TODO: throw appropriate error and redirect 32 | console.log("Error1: " + response.data); 33 | return; 34 | } 35 | var data = response.data; 36 | self.setState({ 37 | role: data.user.role 38 | }); 39 | }) 40 | .catch(function (error) { 41 | console.log(error); 42 | if (error.response) { 43 | if (error.response.status) { 44 | alert("Session timed out."); 45 | window.location.href = '/'; 46 | } 47 | } 48 | }); 49 | var apiPath = '/api/assignments/' + userID + '/courses' 50 | axios.get(apiPath, { 51 | headers: { 52 | 'x-access-token': token, 53 | 'Content-Type': 'application/json' 54 | } 55 | }) 56 | .then(function (response) { 57 | if (!response.data.success) { 58 | // TODO: throw appropriate error and redirect 59 | console.log("Error1: " + response.data); 60 | 61 | } 62 | var data = response.data; 63 | // console.log(data); 64 | self.setState({ isLoading: false }); 65 | self.setState({ 66 | courses: data.courses 67 | }); 68 | var courses = data.courses; 69 | for (var i = 0; i < courses.length; i++) { 70 | var apiPath = '/api/assignments/' + courses[i]._id + '/' + userID + '/new'; 71 | axios.get(apiPath, { 72 | headers: { 73 | 'x-access-token': token, 74 | 'Content-Type': 'application/json' 75 | } 76 | }) 77 | .then(function (response) { 78 | if (!response.data.success) { 79 | console.log("Error1: " + response.data); 80 | } 81 | var data = response.data; 82 | self.setState({ 83 | assignments: self.state.assignments.concat(data.assignments.assignments) 84 | }); 85 | console.log(response.data); 86 | }) 87 | .catch(function (error) { 88 | console.log('Error2: ', error); 89 | }); 90 | }// End of for loop 91 | }) 92 | } 93 | render() { 94 | let content; 95 | const profContent = ( 96 |
97 | { 98 | this.state.assignments.length < 1 && 99 |
Sorry, no assignments found.
100 | } 101 | { 102 | this.state.assignments.map(function (each) { 103 | return 104 | }) 105 | } 106 | 107 |
108 | ); 109 | const studContent = ( 110 |
111 | { 112 | this.state.assignments.length < 1 && 113 |
Sorry, no new assignments found.
114 | } 115 | { 116 | this.state.assignments.map(function (each) { 117 | return 118 | }) 119 | } 120 | 121 |
122 | ); 123 | if (this.state.role == "prof") { 124 | content = profContent; 125 | } 126 | else { 127 | content = studContent; 128 | } 129 | if (this.state.isLoading) 130 | return ; 131 | else 132 | return ( 133 |
{content}
134 | ); 135 | } 136 | } 137 | export default Assignments; -------------------------------------------------------------------------------- /client/app/components/Pages/Assignments/downloadFile.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import axios from 'axios'; 3 | 4 | class downloadFile extends Component { 5 | constructor(props) { 6 | super(props); 7 | } 8 | componentDidMount() { 9 | //api/assignments/:fileID/download 10 | const { match: { params } } = this.props; 11 | 12 | // console.log(params.fileID) 13 | var token = localStorage.getItem('token') 14 | axios.get(`/api/assignments/${params.fileID}/${params.userID}/download`, { 15 | headers: { 16 | 'x-access-token': token, 17 | } 18 | }) 19 | .then(res => console.log(res.data)) 20 | .catch(err => console.log(err)) 21 | 22 | window.close(); 23 | } 24 | render() { 25 | return ( 26 |
27 | 28 |
29 | ) 30 | } 31 | } 32 | 33 | export default downloadFile; -------------------------------------------------------------------------------- /client/app/components/Pages/Assignments/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './Assignments'; -------------------------------------------------------------------------------- /client/app/components/Pages/Assignments/submissionsCard.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import axios from 'axios'; 3 | import { Link } from 'react-router-dom'; 4 | 5 | class SubmissionsCard extends Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = ({ 9 | name: "", 10 | usn: "" 11 | }) 12 | } 13 | 14 | componentDidMount() { 15 | var self = this; 16 | // var token = localStorage.getItem('token'); 17 | var userID = this.props.user; 18 | 19 | var apiPath = '/api/account/' + userID + '/info' 20 | axios.get(apiPath, { 21 | headers: { 22 | 'Content-Type': 'application/json' 23 | } 24 | }) 25 | .then(function (response) { 26 | if (!response.data.success) { 27 | // TODO: throw appropriate error and redirect 28 | console.log("Error1: " + response.data); 29 | return; 30 | } 31 | var data = response.data; 32 | console.log(data); 33 | self.setState({ 34 | name: data.user.name.firstName + ' ' + data.user.name.lastName, 35 | usn: data.user.usn 36 | }); 37 | }) 38 | .catch(function (error) { 39 | console.log('Error2: ', error); 40 | }); 41 | } 42 | openWindow() { 43 | // Download Submission 46 | } 47 | 48 | render() { 49 | let content; 50 | const downloadSubmission = "/api/assignments/" + this.props.fileID + '/'+this.props.user+'/download?token=' + localStorage.getItem('token'); 51 | 52 | const Content = ( 53 |
54 |
55 | 56 |
57 | Name : {this.state.name}
58 | USN : {this.state.usn}

59 | {/* */} 60 | Download 61 |
62 | 63 |
64 |
65 |
66 | ); 67 | content = Content; 68 | return ( 69 |
{content}
70 | 71 | ) 72 | } 73 | } 74 | export default SubmissionsCard; -------------------------------------------------------------------------------- /client/app/components/Pages/Assignments/viewAssignment.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import axios from 'axios'; 3 | 4 | export default class viewAssignment extends Component { 5 | constructor(props) { 6 | super(props); 7 | this.state = { 8 | assignment:[] 9 | }; 10 | } 11 | componentDidMount(){ 12 | var token = localStorage.getItem('token'); 13 | const { match: { params } } = this.props; 14 | // /api/assignments/:assignmentID/details 15 | axios.get(`/api/assignments/${params.assignmentID}/details`) 16 | .then(res=> { 17 | console.log(res); 18 | this.setState({ 19 | assignment: res.data.data.assignment 20 | }) 21 | 22 | }) 23 | .catch(err => console.log(err)) 24 | } 25 | render() { 26 | let content; 27 | const Content = ( 28 |
29 |
30 |
31 |

{this.state.assignment.uniqueID}: {this.state.assignment.name}

32 |
33 | Description: {this.state.assignment.details}
34 | Type: {this.state.assignment.type}
35 | Due Date: {this.state.assignment.dueDate}
36 | Maximum Marks: {this.state.assignment.maxMarks}
37 | Resource URL: {this.state.assignment.resourceUrl}

38 |
39 |
40 |
41 |
42 | 43 |
44 | ); 45 | 46 | 47 | content = Content; 48 | 49 | return ( 50 |
{content}
51 | 52 | ) 53 | } 54 | } 55 | 56 | -------------------------------------------------------------------------------- /client/app/components/Pages/Assignments/viewSubmissions.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import axios from 'axios'; 3 | import SubmissionsCard from '../Assignments/submissionsCard'; 4 | 5 | class viewSubmissions extends Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = ({ 9 | submissions:[] 10 | }) 11 | this.zipFile = this.zipFile.bind(this); 12 | } 13 | 14 | componentDidMount(){ 15 | //get details 16 | 17 | ///api/assignments/:assignmentID/submissions 18 | var token = localStorage.getItem('token') 19 | 20 | axios.get(`/api/assignments/${this.props.location.state.assignmentID}/submissions`, { 21 | headers: { 22 | 'x-access-token': token, 23 | } 24 | }) 25 | .then(res => { 26 | this.setState({ 27 | submissions: res.data.data.assignment.submissions 28 | }) 29 | console.log(this.state.submissions); 30 | }) 31 | .catch(err => console.log(err)) 32 | } 33 | 34 | zipFile(){ 35 | var token = localStorage.getItem('token'); 36 | axios.get(`/api/assignments/${this.props.location.state.assignmentID}/zip`, { 37 | headers: { 38 | 'x-access-token': token, 39 | } 40 | }).then(function(res){ 41 | console.log("Files successfully zipped"); 42 | }).catch(function(err){ 43 | console.log(err); 44 | }) 45 | } 46 | //add usn 47 | render() { 48 | let content; 49 | const zipSubmission = "/api/assignments/" + this.props.location.state.assignmentID +'/zip?token=' + localStorage.getItem('token'); 50 | const Content = ( 51 |
52 | { 53 | this.state.submissions.map(function (each) { 54 | return 55 | }) 56 | } 57 |
58 | 59 |
60 |
61 | ); 62 | content = Content; 63 | return ( 64 |
65 | {content} 66 |
67 | ) 68 | } 69 | } 70 | 71 | export default viewSubmissions; -------------------------------------------------------------------------------- /client/app/components/Pages/Assignments/zipFiles.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import axios from 'axios'; 3 | 4 | class zipFiles extends Component { 5 | constructor(props) { 6 | super(props); 7 | } 8 | componentDidMount() { 9 | //api/assignments/:fileID/download 10 | const { match: { params } } = this.props; 11 | // console.log(params.fileID) 12 | var token = localStorage.getItem('token') 13 | axios.get(`/api/assignments/${params.assignmentID}/zip`, { 14 | headers: { 15 | 'x-access-token': token, 16 | } 17 | }) 18 | .then(res => console.log(res.data)) 19 | .catch(err => console.log(err)) 20 | 21 | window.close(); 22 | } 23 | render() { 24 | return ( 25 |
26 | 27 |
28 | ) 29 | } 30 | } 31 | 32 | export default zipFiles; -------------------------------------------------------------------------------- /client/app/components/Pages/Contests.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import axios from 'axios'; 3 | import ReactLoading from '../common/Loading'; 4 | import ReactTable from "react-table"; 5 | import 'react-table/react-table.css'; 6 | 7 | class Contests extends React.Component { 8 | constructor(props) { 9 | super(props); 10 | this.state = { 11 | isLoading: true, 12 | usn: "", 13 | name: {}, 14 | contender: {}, 15 | globalRankList: [] 16 | }; 17 | } 18 | 19 | 20 | componentDidMount() { 21 | var self = this; 22 | var token = localStorage.getItem('token'); 23 | var userID = localStorage.getItem('user_id'); 24 | 25 | var apiPath = '/api/contests/' + userID + '/contenderInfo'; 26 | axios.get(apiPath, { 27 | headers: { 28 | 'x-access-token': token, 29 | 'Content-Type': 'application/json' 30 | } 31 | }) 32 | .then(function (response) { 33 | if (!response.data.success) { 34 | console.log("Error: " + response.data); 35 | return; 36 | } 37 | var data = response.data.contenderDetails; 38 | self.setState({ 39 | name: data.name, 40 | contender: data.contender 41 | }); 42 | 43 | }) 44 | .catch(function (error) { 45 | console.log(error); 46 | if (error.response) { 47 | if (error.response.status) { 48 | alert("Session timed out."); 49 | window.location.href = '/'; 50 | } 51 | } 52 | }); 53 | 54 | var apiPath = '/api/contests/globalRankList'; 55 | axios.get(apiPath, { 56 | headers: { 57 | 'x-access-token': token, 58 | 'Content-Type': 'application/json' 59 | } 60 | }) 61 | .then(function (response) { 62 | if (!response.data.success) { 63 | console.log("Error: " + response.data); 64 | return; 65 | } 66 | var data = response.data.globalRankList.userContenderDetails; 67 | data.sort(function (a, b) { 68 | return b.rating - a.rating; 69 | }); 70 | self.setState({ isLoading: false }); 71 | self.setState({ 72 | globalRankList: data 73 | }); 74 | 75 | }) 76 | .catch(function (error) { 77 | console.log('Error: ', error); 78 | }); 79 | } 80 | 81 | render() { 82 | 83 | const data = this.state.globalRankList; 84 | 85 | const columns = 86 | [ 87 | { 88 | Header: "Rank", 89 | id: "row", 90 | maxWidth: 65, 91 | filterable: false, 92 | Cell: (row) => { 93 | return
{row.index + 1}
; 94 | } 95 | }, 96 | { 97 | Header: "Name", 98 | accessor: "name" 99 | }, 100 | { 101 | Header: "USN", 102 | accessor: "usn", 103 | maxWidth: 200, 104 | }, 105 | { 106 | Header: "Contests", 107 | accessor: "timesPlayed", 108 | maxWidth: 100, 109 | }, 110 | { 111 | Header: "Rating", 112 | accessor: "rating", 113 | maxWidth: 150, 114 | }, 115 | { 116 | Header: "Best", 117 | accessor: "best", 118 | maxWidth: 150, 119 | } 120 | ] 121 | if (this.state.isLoading) 122 | return ; 123 | else 124 | return ( 125 |
126 |
Contender Details
127 |

Your rating shall be updated after every rated contest. If you have not taken part in any contests, you will see a '-1' indicating the same. For more information about the parameters for this Global Ranking, please visit the link here.

128 |

Name: {this.state.name.firstName} {this.state.name.lastName}

129 |

130 | Rating: {Math.round(this.state.contender.rating)}    131 | Best: {Math.round(this.state.contender.best)}    132 | Contests: {this.state.contender.timesPlayed}    133 |

134 |
135 |
Global Rank List
136 |
137 | 151 |
152 |
Calender
153 |

This calender is curated by Varun Vora. To add this calender to your Google calender, click on the Google icon on the bottom right corner.

154 |
155 | 156 |
157 |
158 | ); 159 | } 160 | } 161 | export default Contests; 162 | -------------------------------------------------------------------------------- /client/app/components/Pages/Contribute.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import axios from "axios"; 3 | import { Link, Redirect } from 'react-router-dom'; 4 | import Avatar from 'react-avatar'; 5 | import ReactLoading from '../common/Loading'; 6 | import { Jumbotron, Row, Col } from 'reactstrap'; 7 | 8 | export default class Contribute extends Component { 9 | componentDidMount() { 10 | window.scrollTo(0, 0); 11 | } 12 | render() { 13 | return ( 14 |
15 | 16 |

Contribute

17 |
Contribute
18 |

This project is Open Source under the MIT Licence and all details can be found at this project's GitHub repository.

19 |

Find the repository here.

20 |

We're hiring!

21 |

If you would like to work with The Alcoding Club's Web Application team, here are some details for you. You can gain visibility by fixing issues found under our GitHub 'Issues' tab. You may also work on a fork of this repository and add a feature. Make sure to send in legitimate pull requests with well documented code. If we like your enhancement, we'll pull your code! Once you're visible to us, a member of our team will get in touch with you for the interview process.

22 |

By working with the Web App team, you'll be exposed to technologies like React, Express and Mongo. You will also be working with specialists in these technologies who are excited to help you start your journey into developement!

23 |

The setup instructions and guidlines for contributing are available at this project's GitHub page. For any queries, feel free to contact any of the authors. May the force be with you.

24 |
25 | {/*
26 |   27 |   28 |   29 |   30 |
*/} 31 | 32 |

Developers

33 | 34 | 35 | 36 | 37 |   38 | 39 | 40 | 41 |

Aniket Nitin Kaulavkar

42 |
43 |

Full-stack, Android, React-native and React Developer. Competitive coding fanatic.

44 |
45 | 46 |
47 | 48 | 49 | 50 | 51 |   52 | 53 | 54 | 55 |

Parth V Shah

56 |
57 |

Technology enthusiast and Full-stack developer.

58 |
59 | 60 |
61 | 62 |
63 | 64 | 65 | 66 | 67 |   68 | 69 | 70 | 71 |

Rishi Ravikumar

72 |
73 |

Full-Stack Developer. Ardent Competitive Coder.

74 |
75 | 76 |
77 | 78 | 79 | 80 | 81 |   82 | 83 | 84 | 85 |

Aditya Vinod Kumar

86 |
87 |

Full-Stack Developer. Machine Learning and Competitive Coding Enthusiast.

88 |
89 | 90 |
91 | 92 |
93 |
94 |
95 | ) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /client/app/components/Pages/Courses/AnchorForm.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import axios from 'axios'; 3 | 4 | class AnchorForm extends React.Component { 5 | constructor() { 6 | super(); 7 | this.state = { 8 | values: [{ profID: "", sections: "" }], 9 | } 10 | this.handleChange = this.handleChange.bind(this); 11 | this.addProf = this.addProf.bind(this); 12 | this.handleSubmit = this.handleSubmit.bind(this); 13 | } 14 | componentDidMount() { 15 | 16 | } 17 | handleChange(e) { 18 | e.preventDefault(); 19 | if (["profID", "sections"].includes(e.target.className)) { 20 | let values = [...this.state.values] 21 | values[e.target.dataset.id][e.target.className] = e.target.value 22 | this.setState({ values }); 23 | } else { 24 | this.setState({ [e.target.name]: e.target.value }) 25 | } 26 | } 27 | addProf(e) { 28 | e.preventDefault(); 29 | this.setState((prevState) => ({ 30 | values: [...prevState.values, { profID: "", sections: "" }], 31 | })); 32 | } 33 | // handleSubmit = async function(e) { 34 | handleSubmit(e) { 35 | e.preventDefault() 36 | this.sendToDb(); 37 | } 38 | 39 | async sendToDb() 40 | { 41 | console.log(this.state.values); 42 | var self = this; 43 | var userID = localStorage.getItem('user_id'); 44 | var token = localStorage.getItem('token'); 45 | var apiPath = 'api/assignments/createCourse'; 46 | var config = { 47 | headers: { 48 | 'x-access-token': token, 49 | 'Content-Type': 'application/json' 50 | } 51 | } 52 | var data = Object.assign({}, self.state.course); 53 | 54 | data.name = self.props.name; 55 | data.code = self.props.code; 56 | data.department = self.props.department; 57 | data.description = self.props.description; 58 | data.resourcesUrl = self.props.resourcesUrl; 59 | var details = { credits: self.props.credits, hours: self.props.hours, isCore: self.props.isCore } 60 | data.details = details; 61 | var duration = { startDate: self.props.startDate, endDate: self.props.endDate } 62 | data.duration = duration; 63 | data.graduating = self.props.graduating; 64 | var values = self.state.values; 65 | var err_flag = false; 66 | // self.state.classes is an array of objects 67 | // Each object has 2 items - ProfessorID and Array of sections just like in Model 68 | // [{professorID: value , sections:[.....]},{professorID:value, sections:[.....]}] 69 | values.forEach(function(classData) { 70 | var body = Object.assign({}, data); 71 | body.professorID = classData.profID; 72 | body.sections = classData.sections; 73 | body.anchorDescription = self.props.anchorDescription; 74 | body.role = "anchor"; 75 | console.log(body); 76 | console.log(classData.profID); 77 | console.log(classData.sections); 78 | axios.post(apiPath, body, config) 79 | .then(res => { 80 | console.log(res.data); 81 | window.location.reload(); 82 | }) 83 | .catch(err => { 84 | console.log(err); 85 | err_flag = true 86 | }) 87 | }); 88 | if(err_flag) alert("Course failed to upload"); 89 | } 90 | render() { 91 | let { values } = this.state 92 | return ( 93 |
94 |
95 |
96 |
Professor IDs
97 |
Sections
98 |
99 | { 100 | values.map((val, idx) => { 101 | let pId = `p-${idx}`, cId = `c-${idx}` 102 | return ( 103 |
104 | {/* */} 105 | 116 | {/* */} 117 | 127 |
128 | ) 129 | }) 130 | } 131 | 132 |
133 | 134 | 135 |
136 | ) 137 | } 138 | } 139 | export default AnchorForm; -------------------------------------------------------------------------------- /client/app/components/Pages/Courses/CourseCard.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link, Redirect } from 'react-router-dom'; 3 | 4 | class CourseCard extends Component { 5 | constructor(props) { 6 | super(props); 7 | this.state = { 8 | courseID: '' 9 | }; 10 | } 11 | componentDidMount() { 12 | this.setState({ 13 | courseID: this.props.courseID 14 | }) 15 | } 16 | 17 | 18 | render() { 19 | let content; 20 | const profContent = ( 21 |
22 |
23 |

{this.props.code}: {this.props.name}

24 |
25 | Credits: {this.props.credits}
26 | Deparment: {this.props.department}
27 | Description: {this.props.description}
28 | Resource URL: {this.props.resourceUrl} 29 |
30 |
31 | View Course 39 |
40 |
41 |
42 |
43 | ); 44 | const studContent = ( 45 |
46 |
47 |

{this.props.code}: {this.props.name}

48 |
49 | Credits: {this.props.credits}
50 | Deparment: {this.props.department}
51 | Description: {this.props.description}
52 | Resource URL: {this.props.resourceUrl} 53 |
54 |
55 |
56 |
57 | ); 58 | if (this.props.role == "prof") { 59 | content = profContent; 60 | } 61 | else { 62 | content = studContent; 63 | } 64 | return ( 65 |
{content}
66 | ) 67 | } 68 | } 69 | 70 | export default CourseCard; 71 | -------------------------------------------------------------------------------- /client/app/components/Pages/Courses/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './Courses'; -------------------------------------------------------------------------------- /client/app/components/Pages/Profile/MutableBox.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | var locale = require('browser-locale')(); 3 | 4 | 5 | class MutableBox extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | isEditing: false, 10 | hasChanged: false 11 | }; 12 | 13 | this.edit = this.edit.bind(this); 14 | this.save = this.save.bind(this); 15 | this.cancel = this.cancel.bind(this); 16 | 17 | } 18 | 19 | edit() { 20 | this.setState({ isEditing: true }); 21 | this.props.changeEditingStatus(1); 22 | } 23 | 24 | save() { 25 | var inp = this.refs.newText.value; 26 | var currentState = Object.assign({}, this.state); 27 | 28 | if (inp) { 29 | currentState.hasChanged = true; 30 | this.props.updateFieldValue(this.props.field, inp); 31 | } 32 | this.props.changeEditingStatus(-1); 33 | currentState.isEditing = false; 34 | this.setState(currentState); 35 | } 36 | 37 | cancel(){ 38 | this.setState({ isEditing: false }); 39 | } 40 | 41 | renderNormal() { 42 | var fieldValue = this.props.val; 43 | if (this.props.field == "dob") { 44 | let date = new Date(this.props.val); 45 | fieldValue = date.toLocaleDateString(locale ? locale : "en-GB"); 46 | } 47 | 48 | var inputStyle = "color:black;" 49 | // if (this.state.hasChanged) { 50 | // inputStyle = "color:red;" 51 | // } 52 | 53 | return ( 54 |
55 |
{this.props.fieldName}
56 |
57 |
{fieldValue}
58 | 59 |
60 |
61 | ) 62 | 63 | } 64 | 65 | 66 | renderEditing() { 67 | var inputType = this.props.inputType; 68 | return ( 69 |
70 |
{this.props.fieldName}
71 |
72 | 73 | 74 | 75 |
76 |
77 | ); 78 | } 79 | 80 | render() { 81 | if (!this.state.isEditing) { return (this.renderNormal()); } 82 | else { return (this.renderEditing()); } 83 | } 84 | } 85 | 86 | export default MutableBox; 87 | -------------------------------------------------------------------------------- /client/app/components/Pages/Profile/PasswordBox.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import axios from 'axios'; 3 | 4 | class PasswordBox extends Component { 5 | constructor() { 6 | super(); 7 | this.state = { 8 | oldPassword: "", 9 | newPassword: "", 10 | confirmNewPassword: "" 11 | }; 12 | 13 | this.changeOldPassword = this.changeOldPassword.bind(this); 14 | this.changeNewPassword = this.changeNewPassword.bind(this); 15 | this.changeConfirmNewPassword = this.changeConfirmNewPassword.bind(this); 16 | this.confirmPasswordChange = this.confirmPasswordChange.bind(this); 17 | } 18 | changeOldPassword(event) { 19 | event.preventDefault(); 20 | this.setState({ 21 | oldPassword: event.target.value, 22 | }); 23 | 24 | } 25 | 26 | changeNewPassword(event) { 27 | event.preventDefault(); 28 | this.setState({ 29 | newPassword: event.target.value, 30 | }); 31 | 32 | } 33 | 34 | changeConfirmNewPassword(event) { 35 | event.preventDefault(); 36 | this.setState({ 37 | confirmNewPassword: event.target.value, 38 | }); 39 | 40 | } 41 | 42 | 43 | confirmPasswordChange() { 44 | //api call to change password comes here 45 | var userID = localStorage.getItem("user_id"); 46 | var token = localStorage.getItem('token'); 47 | var body = { 48 | oldPassword: this.state.oldPassword, 49 | newPassword: this.state.newPassword 50 | } 51 | //api/account/:userID/password 52 | if (this.state.newPassword != this.state.confirmNewPassword) { 53 | alert("Passwords do not match."); 54 | } 55 | else if (this.state.oldPassword == this.state.newPassword) { 56 | alert("New Password cannot be same as old password."); 57 | } 58 | else { 59 | axios.post(`api/account/${userID}/changePassword`, body, { 60 | headers: { 61 | 'x-access-token': token, 62 | 'Content-Type': 'application/json' 63 | } 64 | }).then(res => { 65 | 66 | if (res.data.success) { 67 | console.log(res.data); 68 | alert("Password change successfull!") 69 | window.location.reload(); 70 | } 71 | }) 72 | .catch(err => { 73 | console.log(err); 74 | alert("Error changing the password. Please try again."); 75 | }) 76 | } 77 | } 78 | 79 | render() { 80 | return( 81 |
82 | 83 | 84 | 135 |
136 | ) 137 | } 138 | } 139 | 140 | export default PasswordBox; 141 | -------------------------------------------------------------------------------- /client/app/components/Pages/Profile/Profile.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link, Redirect } from 'react-router-dom'; 3 | import axios from 'axios'; 4 | import { connect } from 'react-redux'; 5 | import ReactLoading from '../../common/Loading'; 6 | import StaticBox from './StaticBox.js'; 7 | import MutableBox from './MutableBox.js'; 8 | import PasswordBox from './PasswordBox.js'; 9 | import { ToastContainer, ToastStore } from 'react-toasts'; 10 | import { _API_CALL } from './../../../Utils/api'; 11 | 12 | 13 | 14 | 15 | class Profile extends React.Component { 16 | constructor() { 17 | super(); 18 | this.state = { 19 | isLoading: true, 20 | isEditing: 0, 21 | usn: "", 22 | name: "", 23 | basicInfo: {} 24 | }; 25 | this.updateValue = this.updateValue.bind(this); 26 | this.updateUsername = this.updateUsername.bind(this); 27 | this.changeEditingStatus = this.changeEditingStatus.bind(this); 28 | } 29 | 30 | 31 | componentDidMount() { 32 | var self = this; 33 | var token = localStorage.getItem('token') 34 | var userID = localStorage.getItem('user_id') 35 | 36 | var apiPath = '/api/account/' + userID + '/details'; 37 | _API_CALL(apiPath, "GET", {}, token) 38 | .then(response => { 39 | var data = response.data; 40 | self.setState({ isLoading: false }); 41 | self.setState({ 42 | usn: data.user.usn, 43 | name: data.user.name.firstName + " " + data.user.name.lastName, 44 | basicInfo: data.user.basicInfo 45 | }); 46 | }) 47 | .catch(error => { 48 | console.log(error); 49 | }) 50 | } 51 | 52 | updateUsername(field, newVal) { 53 | var self = this; 54 | var token = localStorage.getItem('token') 55 | var userID = localStorage.getItem('user_id') 56 | var apiPath = '/api/account/' + userID + '/username' 57 | var body = { username: newVal }; 58 | var previous_username = this.state.username; 59 | this.setState({ "username": newVal }) 60 | if (newVal == previous_username) { 61 | ToastStore.warning("Current username. Please try another one"); 62 | return; 63 | } 64 | 65 | _API_CALL(apiPath, "POST", body, token) 66 | .then(response => { 67 | if (response.status == 200) { 68 | this.setState({ username: newVal }); 69 | ToastStore.success('Successfully updated!'); 70 | } else { 71 | throw new Error("Unsucessful") 72 | } 73 | }) 74 | .catch(error => { 75 | self.setState({ "username": previous_username }); 76 | if (error.response.status == 404) { 77 | ToastStore.warning("Username already exists. Please try another one"); 78 | } 79 | else if (error.message) { 80 | ToastStore.error(error.message); 81 | } 82 | }); 83 | } 84 | 85 | updateValue(field, newVal) { 86 | // TODO: Verify email and phone existance. 87 | console.log(this.state) 88 | var basicInfoCopy = Object.assign({}, this.state.basicInfo); 89 | if (basicInfoCopy[field] == newVal) { 90 | ToastStore.warning("Current " + field + ". Please try another one"); 91 | return; //^ If old value equals updated value, displays appropriate error 92 | } 93 | if (field == "phone") { 94 | if (newVal[0] != '+') { 95 | newVal = '+91' + newVal; 96 | } 97 | var phoneFormat = new RegExp(/^((\+){1}91){1}[6-9]{1}[0-9]{9}$/); 98 | if (!phoneFormat.test(newVal)) { 99 | ToastStore.warning("Invalid Phone Number. Please try another one"); 100 | return; //^ If phone number isn't of format - [6-9]{1}[1-9]{9} 101 | } 102 | } 103 | else if (field == "email") { 104 | var emailFormat = new RegExp(/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/); 105 | if (!emailFormat.test(newVal)) { 106 | ToastStore.warning("Invalid Email ID. Please try another one"); 107 | return; //^ If email ID isn't of format - {x@y.z} 108 | } 109 | } 110 | else if (field == "dob") { 111 | var givenDob = new Date(newVal); 112 | var presentDate = new Date(); 113 | if (presentDate - givenDob < 16 * 365 * 24 * 3600 * 1000 || presentDate - givenDob > 65 * 365 * 24 * 3600 * 1000) { 114 | ToastStore.warning("Invalid Date of Birth. Please try another one"); 115 | return; //^ If user is less than 16 years or greater than 65 years 116 | } 117 | } 118 | basicInfoCopy[field] = newVal; 119 | this.setState({ basicInfo: basicInfoCopy }); 120 | var token = localStorage.getItem('token') 121 | var userID = localStorage.getItem('user_id') 122 | var apiPath = '/api/account/' + userID + '/basicInfo'; 123 | var body = new Object(); 124 | body["phone"] = basicInfoCopy.phone; 125 | body["email"] = basicInfoCopy.email; 126 | body["dob"] = basicInfoCopy.dob; 127 | axios.put( 128 | apiPath, 129 | body, 130 | { 131 | headers: { 132 | 'x-access-token': token, 133 | 'Content-Type': 'application/json' 134 | } 135 | }) 136 | .then(function (response) { 137 | if (!response.data.success) { 138 | // TODO: throw appropriate error and redirect 139 | console.log("Error: " + response.data); 140 | return; 141 | } 142 | else { 143 | // TODO: redirect to this page(profile) 144 | console.log(response.data); 145 | ToastStore.success('Successfully updated!'); 146 | } 147 | }) 148 | .catch(function (error) { 149 | // TODO: Try again after sometime? 150 | console.log('error is ', error); 151 | }); 152 | } 153 | 154 | changeEditingStatus(value) { 155 | this.state.isEditing += value; 156 | } 157 | 158 | changeEditingStatus(value) { 159 | this.state.isEditing += value; 160 | } 161 | 162 | render() { 163 | const { isAuthenticated, user } = this.props.auth; 164 | 165 | const content = ( 166 |
167 |
Profile
168 | 169 |
170 | 171 | 172 |

173 | Update Contest Handles 174 | 175 |

176 |
177 | 178 |
179 | 180 |
181 | 182 |
183 | 184 | 185 | 186 | {/* */} 187 |
188 | ); 189 | 190 | if (this.state.isLoading) 191 | return ; 192 | else 193 | return ( 194 |
195 |
196 | {content} 197 |
198 |
199 | {content} 200 |
201 |
202 | ); 203 | } 204 | } 205 | 206 | 207 | const mapStateToProps = state => ({ 208 | auth: state.auth, 209 | errors: state.errors 210 | }); 211 | 212 | 213 | export default connect(mapStateToProps)(Profile); 214 | -------------------------------------------------------------------------------- /client/app/components/Pages/Profile/PublicProfile.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link, Redirect } from 'react-router-dom'; 3 | import axios from 'axios'; 4 | import { connect } from 'react-redux'; 5 | import ReactLoading from 'react-loading'; 6 | import StaticBox from './StaticBox.js'; 7 | 8 | 9 | class PublicProfile extends React.Component { 10 | constructor() { 11 | super(); 12 | this.state = { 13 | isLoading: true, 14 | name: "", 15 | usn: "", 16 | rating: 0, 17 | best: 0, 18 | role: "" 19 | }; 20 | } 21 | 22 | componentDidMount() { 23 | var self = this; 24 | const { match: { params } } = this.props; 25 | 26 | var apiPath = `/api/users/${params.username}` 27 | axios.get(apiPath, { 28 | headers: { 29 | 'Content-Type': 'application/json' 30 | } 31 | }) 32 | .then(function (response) { 33 | if (!response.data.success) { 34 | // TODO: throw appropriate error and redirect 35 | console.log("Error:" + response.data); 36 | return; 37 | } 38 | console.log(response.data); 39 | var data = response.data.user; 40 | self.setState({ isLoading: false }); 41 | self.setState({ 42 | usn: data.usn, 43 | name: data.name.firstName + " " + data.name.lastName, 44 | rating: data.rating, 45 | best: data.best, 46 | role: data.role 47 | }); 48 | }) 49 | .catch(function (error) { 50 | // TODO: Show Not Found Page for this URL 51 | console.log('error is ', error); 52 | if (error.response) { 53 | if (error.response.status) { 54 | alert("The username doesn't exist"); 55 | window.location.href = '/'; 56 | } 57 | } 58 | }); 59 | } 60 | 61 | render() { 62 | const studentContent = ( 63 |
64 | 65 | 66 | 67 |
68 | ); 69 | const professorContent = ( 70 |
71 | 72 |
73 | // TODO: Add more details of allotted courses etc 74 | ); 75 | const { isAuthenticated, user } = this.props.auth; 76 | if (this.state.isLoading) 77 | return ; 78 | else 79 | return ( 80 |
81 |
82 |
Profile
83 |
84 | 85 | 86 | {this.state.role=="student"? studentContent: professorContent} 87 |

88 |

89 |
90 |
91 | ); 92 | } 93 | } 94 | 95 | const mapStateToProps = state => ({ 96 | auth: state.auth, 97 | errors: state.errors 98 | }); 99 | 100 | export default connect(mapStateToProps)(PublicProfile); -------------------------------------------------------------------------------- /client/app/components/Pages/Profile/StaticBox.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | class StaticBox extends React.Component { 4 | constructor(props) { 5 | super(props); 6 | } 7 | render() { 8 | var fieldValue = this.props.val; 9 | return ( 10 |
11 |
{this.props.fieldName}: {fieldValue}
12 |
13 | ); 14 | } 15 | } 16 | 17 | export default StaticBox; 18 | -------------------------------------------------------------------------------- /client/app/components/Pages/Profile/UpdateHandle.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import MutableBox from './MutableBox' 3 | import ReactLoading from '../../common/Loading'; 4 | import axios from 'axios'; 5 | import { connect } from 'react-redux'; 6 | import { ToastContainer, ToastStore } from 'react-toasts'; 7 | 8 | class updateHandle extends React.Component { 9 | constructor() { 10 | super(); 11 | this.state = { 12 | isLoading: true, 13 | codechef: "", 14 | codejam: "", 15 | kickstart: "", 16 | hackerEarth: "", 17 | hackerRank: "", 18 | codeforces: "", 19 | spoj: "" 20 | }; 21 | this.updateValue = this.updateValue.bind(this); 22 | } 23 | 24 | componentDidMount() { 25 | var self = this; 26 | var token = localStorage.getItem('token'); 27 | var userID = localStorage.getItem('user_id'); 28 | 29 | var apiPath = '/api/account/' + userID + '/details' 30 | axios.get(apiPath, { 31 | headers: { 32 | 'x-access-token': token, 33 | 'Content-Type': 'application/json' 34 | } 35 | }) 36 | .then(function (response) { 37 | if (!response.data.success) { 38 | // TODO: throw appropriate error and redirect 39 | console.log("Error: " + response.data); 40 | return; 41 | } 42 | var data = response.data.user.contender; 43 | var handles = data.handles; 44 | if (!handles) { 45 | handles = new Object(); 46 | } 47 | handles.codechef = (!handles.codechef) ? "" : handles.codechef; 48 | handles.codejam = (!handles.codejam) ? "" : handles.codejam; 49 | handles.kickstart = (!handles.kickstart) ? "" : handles.kickstart; 50 | handles.hackerEarth = (!handles.hackerEarth) ? "" : handles.hackerEarth; 51 | handles.hackerRank = (!handles.hackerRank) ? "" : handles.hackerRank; 52 | handles.codeforces = (!handles.codeforces) ? "" : handles.codeforces; 53 | handles.spoj = (!handles.spoj) ? "" : handles.spoj; 54 | 55 | self.setState({ 56 | isLoading: false, 57 | codechef: handles.codechef, 58 | codejam: handles.codejam, 59 | kickstart: handles.kickstart, 60 | hackerEarth: handles.hackerEarth, 61 | hackerRank: handles.hackerRank, 62 | codeforces: handles.codeforces, 63 | spoj: handles.spoj 64 | }); 65 | }) 66 | .catch(function (error) { 67 | // TODO: Try again after sometime? 68 | console.log('error is ', error); 69 | if (error.response) { 70 | if (error.response.status) { 71 | alert("Session timed out."); 72 | window.location.href = '/'; 73 | } 74 | } 75 | }); 76 | } 77 | 78 | updateValue(field, newVal) { 79 | var token = localStorage.getItem('token'); 80 | var userID = localStorage.getItem('user_id'); 81 | var stateObj = Object.assign({}, this.state); 82 | stateObj[field] = newVal; 83 | this.setState(stateObj); 84 | 85 | var apiPath = '/api/contests/' + userID + '/codingHandle'; 86 | var body = new Object(); 87 | body["codechef"] = stateObj.codechef; 88 | body["codejam"] = stateObj.codejam; 89 | body["kickstart"] = stateObj.kickstart; 90 | body["spoj"] = stateObj.spoj; 91 | body["hackerRank"] = stateObj.hackerRank; 92 | body["codeforces"] = stateObj.codeforces; 93 | body["hackerEarth"] = stateObj.hackerEarth; 94 | 95 | axios.put( 96 | apiPath, 97 | body, 98 | { 99 | headers: { 100 | 'x-access-token': token, 101 | 'Content-Type': 'application/json' 102 | } 103 | }) 104 | .then(function (response) { 105 | console.log(response.data); 106 | ToastStore.success('Successfully updated!'); 107 | 108 | }) 109 | .catch(function (err) { 110 | console.log(err); 111 | ToastStore.error("Error ocurred. Please try after a while."); 112 | }) 113 | } 114 | 115 | changeEditingStatus(value) { 116 | //Empty function 117 | } 118 | 119 | render() { 120 | const content = ( 121 |
122 |
Coding handles
123 |
Keep it updated to be recognised in the contest ranking.
124 |
125 | 126 | www.codechef.com 127 |
128 | 129 | codeforces.com 130 |
131 | 132 | codejam.withgoogle.com/codejam 133 |
134 | 135 | code.google.com/codejam/kickstart 136 |
137 | 138 | www.hackerearth.com 139 |
140 | 141 | www.hackerrank.com 142 |
143 | 144 | www.spoj.com 145 |
146 | 147 |
148 | ); 149 | 150 | if (this.state.isLoading) 151 | return ; 152 | else 153 | return ( 154 |
155 |
156 | {content} 157 |
158 |
159 | {content} 160 |
161 |
162 | ); 163 | } 164 | } 165 | 166 | const mapStateToProps = state => ({ 167 | auth: state.auth, 168 | errors: state.errors 169 | }); 170 | 171 | 172 | export default connect(mapStateToProps)(updateHandle); -------------------------------------------------------------------------------- /client/app/components/Pages/Profile/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './Profile'; -------------------------------------------------------------------------------- /client/app/components/common/Loading.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import ReactLoading from 'react-loading'; 3 | 4 | class Loading extends React.Component { 5 | constructor(props) { 6 | super(props); 7 | } 8 | render() { 9 | return ( 10 |
11 | {/*
*/} 12 |
13 |
14 | 21 | {/* Loading.. */} 22 |
23 | {/*
*/} 24 |
25 |
26 | ); 27 | } 28 | } 29 | 30 | export default Loading; 31 | -------------------------------------------------------------------------------- /client/app/components/common/PrivateRoute.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, Redirect } from 'react-router-dom'; 3 | import { connect } from 'react-redux'; 4 | import {PropTypes} from 'prop-types'; 5 | 6 | const PrivateRoute = ({ component: Component, auth, ...rest }) => ( 7 | 10 | auth.isAuthenticated === true ? ( 11 | 12 | ) : ( 13 | 14 | ) 15 | } 16 | /> 17 | ); 18 | 19 | PrivateRoute.propTypes = { 20 | auth: PropTypes.object.isRequired 21 | }; 22 | 23 | const mapStateToProps = state => ({ 24 | auth: state.auth 25 | }); 26 | 27 | export default connect(mapStateToProps)(PrivateRoute); 28 | -------------------------------------------------------------------------------- /client/app/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import { 4 | BrowserRouter as Router, 5 | Route, 6 | Link, 7 | Switch 8 | } from 'react-router-dom' 9 | import { PersistGate } from 'redux-persist/integration/react'; 10 | import './styles/styles.scss'; 11 | import { setCurrentUser, logoutUser, loginUser } from './actions/authActions'; 12 | import { Provider } from 'react-redux'; 13 | import { store, persistor } from '../app/store/store'; 14 | import PrivateRoute from '../app/components/common/PrivateRoute'; 15 | import App from '../app/components/App'; 16 | import Home from '../app/components/Home'; 17 | import Profile from '../app/components/Pages/Profile'; 18 | import PublicProfile from '../app/components/Pages/Profile/PublicProfile'; 19 | import Assignments from '../app/components/Pages/Assignments'; 20 | import Contests from '../app/components/Pages/Contests'; 21 | import Courses from '../app/components/Pages/Courses'; 22 | import NotFound from './components/App/NotFound'; 23 | import SignupForm from '../app/components/Admin/SignupForm'; 24 | import AssignmentAdd from '../app/components/Pages/Courses/AddAssignment' 25 | import viewSubmissions from './components/Pages/Assignments/viewSubmissions'; 26 | import viewAssignment from './components/Pages/Assignments/viewAssignment'; 27 | import ForgotPassword from './components/Layout/ForgotPassword'; 28 | import ChangePassword from './components/Layout/ChangePassword'; 29 | import downloadFile from './components/Pages/Assignments/downloadFile'; 30 | import zipFiles from './components/Pages/Assignments/zipFiles'; 31 | import updateHandle from './components/Pages/Profile/UpdateHandle'; 32 | import contribute from './components/Pages/Contribute'; 33 | import ReactLoading from './components/common/Loading' 34 | // import 'bootstrap/dist/css/bootstrap.min.css'; 35 | 36 | 37 | // if (store.getState().auth.isAuthenticated) { 38 | // // store.dispatch(logoutUser()); 39 | // store.dispatch(setCurrentUser(store.getState().token, store.getState().user_id)); 40 | // } 41 | 42 | render(( 43 | 44 | } persistor={persistor}> 45 | 46 | 47 | 48 |
49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 |
86 |
87 |
88 |
89 |
90 | ), document.getElementById('app')); 91 | -------------------------------------------------------------------------------- /client/app/reducers/authReducer.js: -------------------------------------------------------------------------------- 1 | import { SET_CURRENT_USER, SET_DETAILS, LOGOUT_USER } from '../actions/types'; 2 | import isEmpty from '../Utils/isEmpty'; 3 | 4 | const initialState = { 5 | isAuthenticated: false, 6 | token: "", 7 | user_id: "", 8 | name: {} 9 | }; 10 | export default function (state = initialState, action) { 11 | // console.log(action); 12 | switch (action.type) { 13 | case SET_CURRENT_USER: 14 | return { 15 | ...state, 16 | isAuthenticated: !isEmpty(action.payload.token), 17 | user_id: action.payload.user_id, 18 | token: action.payload.token 19 | }; 20 | case SET_DETAILS: 21 | return { 22 | ...state, 23 | name: action.payload.name 24 | }; 25 | 26 | case LOGOUT_USER: 27 | return initialState; 28 | 29 | default: 30 | return state; 31 | } 32 | } -------------------------------------------------------------------------------- /client/app/reducers/errorReducer.js: -------------------------------------------------------------------------------- 1 | import { GET_ERRORS, CLEAR_ERRORS } from '../actions/types'; 2 | 3 | const initialState = {}; 4 | 5 | export default function(state = initialState, action) { 6 | switch (action.type) { 7 | case GET_ERRORS: 8 | return action.payload; 9 | case CLEAR_ERRORS: 10 | return {}; 11 | default: 12 | return state; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /client/app/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import authReducer from './authReducer' 3 | import errorReducer from './errorReducer'; 4 | 5 | export default combineReducers({ 6 | auth: authReducer, 7 | errors: errorReducer 8 | }); 9 | -------------------------------------------------------------------------------- /client/app/store/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './store' -------------------------------------------------------------------------------- /client/app/store/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from "redux"; 2 | import rootReducer from "../reducers"; 3 | import thunk from "redux-thunk"; 4 | import { composeWithDevTools } from 'redux-devtools-extension'; 5 | import { persistReducer, persistStore } from 'redux-persist'; 6 | import storage from 'redux-persist/lib/storage' // defaults to localStorage for web and AsyncStorage for react-native 7 | 8 | 9 | const initialState = {} 10 | const middleware = [thunk]; 11 | const persistConfig = { 12 | key: 'root', 13 | storage, 14 | } 15 | const persistedReducer = persistReducer(persistConfig, rootReducer); 16 | 17 | const store = createStore(persistedReducer, initialState, composeWithDevTools(applyMiddleware(...middleware))); 18 | const persistor = persistStore(store); 19 | 20 | // const store = createStore(rootReducer, initialState, applyMiddleware(...middleware)); 21 | export { store, persistor } -------------------------------------------------------------------------------- /client/app/styles/styles.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | @import url("vendor/normalize.css"); 4 | 5 | /* Custom colors */ 6 | $theme-colors: ( 7 | // "navbar-dark": #002358 //t1 8 | // "navbar-dark": #A83738 //t2 9 | // "navbar-dark": #225378 //t3 10 | // "navbar-dark": #5f63bd //t4 11 | "navbar-dark": #212529, //t5 12 | "bg-dark": #212529 //t5 13 | ); 14 | 15 | /* bootstrap override color variables */ 16 | // $body-bg: #007A9B; //t1 17 | // $body-bg: #D95242; //t2 18 | // $body-bg: #ACF0F2; //t3 19 | // $body-bg: #87cef5; //t4 20 | // $body-bg: #212529; //t5 21 | // #ACF0F2; 22 | // $primary: #2E418C; 23 | // $secondary: #6c757d; 24 | // $success: #28a745; 25 | // $info: #17a2b8; 26 | // $warning: #ffc107; 27 | // $danger: #dc3545; 28 | $light: #ffffff; 29 | $dark: #2188b6; 30 | // $white: #05DBFE; 31 | 32 | @import "node_modules/bootstrap/scss/bootstrap.scss"; -------------------------------------------------------------------------------- /client/app/styles/vendor/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v4.1.1 | MIT License | github.com/necolas/normalize.css */ 2 | 3 | /** 4 | * 1. Change the default font family in all browsers (opinionated). 5 | * 2. Prevent adjustments of font size after orientation changes in IE and iOS. 6 | */ 7 | 8 | html { 9 | font-family: sans-serif; /* 1 */ 10 | -ms-text-size-adjust: 100%; /* 2 */ 11 | -webkit-text-size-adjust: 100%; /* 2 */ 12 | } 13 | 14 | /** 15 | * Remove the margin in all browsers (opinionated). 16 | */ 17 | 18 | body { 19 | margin: 0; 20 | } 21 | 22 | /* HTML5 display definitions 23 | ========================================================================== */ 24 | 25 | /** 26 | * Add the correct display in IE 9-. 27 | * 1. Add the correct display in Edge, IE, and Firefox. 28 | * 2. Add the correct display in IE. 29 | */ 30 | 31 | article, 32 | aside, 33 | details, /* 1 */ 34 | figcaption, 35 | figure, 36 | footer, 37 | header, 38 | main, /* 2 */ 39 | menu, 40 | nav, 41 | section, 42 | summary { /* 1 */ 43 | display: block; 44 | } 45 | 46 | /** 47 | * Add the correct display in IE 9-. 48 | */ 49 | 50 | audio, 51 | canvas, 52 | progress, 53 | video { 54 | display: inline-block; 55 | } 56 | 57 | /** 58 | * Add the correct display in iOS 4-7. 59 | */ 60 | 61 | audio:not([controls]) { 62 | display: none; 63 | height: 0; 64 | } 65 | 66 | /** 67 | * Add the correct vertical alignment in Chrome, Firefox, and Opera. 68 | */ 69 | 70 | progress { 71 | vertical-align: baseline; 72 | } 73 | 74 | /** 75 | * Add the correct display in IE 10-. 76 | * 1. Add the correct display in IE. 77 | */ 78 | 79 | template, /* 1 */ 80 | [hidden] { 81 | display: none; 82 | } 83 | 84 | /* Links 85 | ========================================================================== */ 86 | 87 | /** 88 | * 1. Remove the gray background on active links in IE 10. 89 | * 2. Remove gaps in links underline in iOS 8+ and Safari 8+. 90 | */ 91 | 92 | a { 93 | background-color: transparent; /* 1 */ 94 | -webkit-text-decoration-skip: objects; /* 2 */ 95 | } 96 | 97 | /** 98 | * Remove the outline on focused links when they are also active or hovered 99 | * in all browsers (opinionated). 100 | */ 101 | 102 | a:active, 103 | a:hover { 104 | outline-width: 0; 105 | } 106 | 107 | /* Text-level semantics 108 | ========================================================================== */ 109 | 110 | /** 111 | * 1. Remove the bottom border in Firefox 39-. 112 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. 113 | */ 114 | 115 | abbr[title] { 116 | border-bottom: none; /* 1 */ 117 | text-decoration: underline; /* 2 */ 118 | text-decoration: underline dotted; /* 2 */ 119 | } 120 | 121 | /** 122 | * Prevent the duplicate application of `bolder` by the next rule in Safari 6. 123 | */ 124 | 125 | b, 126 | strong { 127 | font-weight: inherit; 128 | } 129 | 130 | /** 131 | * Add the correct font weight in Chrome, Edge, and Safari. 132 | */ 133 | 134 | b, 135 | strong { 136 | font-weight: bolder; 137 | } 138 | 139 | /** 140 | * Add the correct font style in Android 4.3-. 141 | */ 142 | 143 | dfn { 144 | font-style: italic; 145 | } 146 | 147 | /** 148 | * Correct the font size and margin on `h1` elements within `section` and 149 | * `article` contexts in Chrome, Firefox, and Safari. 150 | */ 151 | 152 | h1 { 153 | font-size: 2em; 154 | margin: 0.67em 0; 155 | } 156 | 157 | /** 158 | * Add the correct background and color in IE 9-. 159 | */ 160 | 161 | mark { 162 | background-color: #ff0; 163 | color: #000; 164 | } 165 | 166 | /** 167 | * Add the correct font size in all browsers. 168 | */ 169 | 170 | small { 171 | font-size: 80%; 172 | } 173 | 174 | /** 175 | * Prevent `sub` and `sup` elements from affecting the line height in 176 | * all browsers. 177 | */ 178 | 179 | sub, 180 | sup { 181 | font-size: 75%; 182 | line-height: 0; 183 | position: relative; 184 | vertical-align: baseline; 185 | } 186 | 187 | sub { 188 | bottom: -0.25em; 189 | } 190 | 191 | sup { 192 | top: -0.5em; 193 | } 194 | 195 | /* Embedded content 196 | ========================================================================== */ 197 | 198 | /** 199 | * Remove the border on images inside links in IE 10-. 200 | */ 201 | 202 | img { 203 | border-style: none; 204 | } 205 | 206 | /** 207 | * Hide the overflow in IE. 208 | */ 209 | 210 | svg:not(:root) { 211 | overflow: hidden; 212 | } 213 | 214 | /* Grouping content 215 | ========================================================================== */ 216 | 217 | /** 218 | * 1. Correct the inheritance and scaling of font size in all browsers. 219 | * 2. Correct the odd `em` font sizing in all browsers. 220 | */ 221 | 222 | code, 223 | kbd, 224 | pre, 225 | samp { 226 | font-family: monospace, monospace; /* 1 */ 227 | font-size: 1em; /* 2 */ 228 | } 229 | 230 | /** 231 | * Add the correct margin in IE 8. 232 | */ 233 | 234 | figure { 235 | margin: 1em 40px; 236 | } 237 | 238 | /** 239 | * 1. Add the correct box sizing in Firefox. 240 | * 2. Show the overflow in Edge and IE. 241 | */ 242 | 243 | hr { 244 | box-sizing: content-box; /* 1 */ 245 | height: 0; /* 1 */ 246 | overflow: visible; /* 2 */ 247 | } 248 | 249 | /* Forms 250 | ========================================================================== */ 251 | 252 | /** 253 | * 1. Change font properties to `inherit` in all browsers (opinionated). 254 | * 2. Remove the margin in Firefox and Safari. 255 | */ 256 | 257 | button, 258 | input, 259 | select, 260 | textarea { 261 | font: inherit; /* 1 */ 262 | margin: 0; /* 2 */ 263 | } 264 | 265 | /** 266 | * Restore the font weight unset by the previous rule. 267 | */ 268 | 269 | optgroup { 270 | font-weight: bold; 271 | } 272 | 273 | /** 274 | * Show the overflow in IE. 275 | * 1. Show the overflow in Edge. 276 | */ 277 | 278 | button, 279 | input { /* 1 */ 280 | overflow: visible; 281 | } 282 | 283 | /** 284 | * Remove the inheritance of text transform in Edge, Firefox, and IE. 285 | * 1. Remove the inheritance of text transform in Firefox. 286 | */ 287 | 288 | button, 289 | select { /* 1 */ 290 | text-transform: none; 291 | } 292 | 293 | /** 294 | * 1. Prevent a WebKit bug where (2) destroys native `audio` and `video` 295 | * controls in Android 4. 296 | * 2. Correct the inability to style clickable types in iOS and Safari. 297 | */ 298 | 299 | button, 300 | html [type="button"], /* 1 */ 301 | [type="reset"], 302 | [type="submit"] { 303 | -webkit-appearance: button; /* 2 */ 304 | } 305 | 306 | /** 307 | * Remove the inner border and padding in Firefox. 308 | */ 309 | 310 | button::-moz-focus-inner, 311 | [type="button"]::-moz-focus-inner, 312 | [type="reset"]::-moz-focus-inner, 313 | [type="submit"]::-moz-focus-inner { 314 | border-style: none; 315 | padding: 0; 316 | } 317 | 318 | /** 319 | * Restore the focus styles unset by the previous rule. 320 | */ 321 | 322 | button:-moz-focusring, 323 | [type="button"]:-moz-focusring, 324 | [type="reset"]:-moz-focusring, 325 | [type="submit"]:-moz-focusring { 326 | outline: 1px dotted ButtonText; 327 | } 328 | 329 | /** 330 | * Change the border, margin, and padding in all browsers (opinionated). 331 | */ 332 | 333 | fieldset { 334 | border: 1px solid #c0c0c0; 335 | margin: 0 2px; 336 | padding: 0.35em 0.625em 0.75em; 337 | } 338 | 339 | /** 340 | * 1. Correct the text wrapping in Edge and IE. 341 | * 2. Correct the color inheritance from `fieldset` elements in IE. 342 | * 3. Remove the padding so developers are not caught out when they zero out 343 | * `fieldset` elements in all browsers. 344 | */ 345 | 346 | legend { 347 | box-sizing: border-box; /* 1 */ 348 | color: inherit; /* 2 */ 349 | display: table; /* 1 */ 350 | max-width: 100%; /* 1 */ 351 | padding: 0; /* 3 */ 352 | white-space: normal; /* 1 */ 353 | } 354 | 355 | /** 356 | * Remove the default vertical scrollbar in IE. 357 | */ 358 | 359 | textarea { 360 | overflow: auto; 361 | } 362 | 363 | /** 364 | * 1. Add the correct box sizing in IE 10-. 365 | * 2. Remove the padding in IE 10-. 366 | */ 367 | 368 | [type="checkbox"], 369 | [type="radio"] { 370 | box-sizing: border-box; /* 1 */ 371 | padding: 0; /* 2 */ 372 | } 373 | 374 | /** 375 | * Correct the cursor style of increment and decrement buttons in Chrome. 376 | */ 377 | 378 | [type="number"]::-webkit-inner-spin-button, 379 | [type="number"]::-webkit-outer-spin-button { 380 | height: auto; 381 | } 382 | 383 | /** 384 | * 1. Correct the odd appearance in Chrome and Safari. 385 | * 2. Correct the outline style in Safari. 386 | */ 387 | 388 | [type="search"] { 389 | -webkit-appearance: textfield; /* 1 */ 390 | outline-offset: -2px; /* 2 */ 391 | } 392 | 393 | /** 394 | * Remove the inner padding and cancel buttons in Chrome and Safari on OS X. 395 | */ 396 | 397 | [type="search"]::-webkit-search-cancel-button, 398 | [type="search"]::-webkit-search-decoration { 399 | -webkit-appearance: none; 400 | } 401 | 402 | /** 403 | * Correct the text style of placeholders in Chrome, Edge, and Safari. 404 | */ 405 | 406 | ::-webkit-input-placeholder { 407 | color: inherit; 408 | opacity: 0.54; 409 | } 410 | 411 | /** 412 | * 1. Correct the inability to style clickable types in iOS and Safari. 413 | * 2. Change font properties to `inherit` in Safari. 414 | */ 415 | 416 | ::-webkit-file-upload-button { 417 | -webkit-appearance: button; /* 1 */ 418 | font: inherit; /* 2 */ 419 | } 420 | -------------------------------------------------------------------------------- /client/public/assets/img/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pes-alcoding-club/alcoding-web-portal/3ba6c73aa857f66aa57795c94a622a3d15a3c6c7/client/public/assets/img/background.png -------------------------------------------------------------------------------- /client/public/assets/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pes-alcoding-club/alcoding-web-portal/3ba6c73aa857f66aa57795c94a622a3d15a3c6c7/client/public/assets/img/logo.png -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | The Alcoding Club 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /config/config.example.js: -------------------------------------------------------------------------------- 1 | // Copy this file as config.js in the same folder, with the proper database connection URI. 2 | 3 | module.exports = { 4 | db: 'mongodb://username:password@url:port/db', 5 | db_dev: 'mongodb://url:port/db', 6 | email: 'JohnDoe@example.com:key', 7 | host_url: 'http://domainName.com/' 8 | }; 9 | -------------------------------------------------------------------------------- /config/helpers.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | // Helper functions 4 | function root(args) { 5 | args = Array.prototype.slice.call(arguments, 0); 6 | return path.join.apply(path, [__dirname].concat('../', ...args)); 7 | } 8 | 9 | exports.root = root; 10 | -------------------------------------------------------------------------------- /config/webpack.common.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const autoprefixer = require('autoprefixer'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 5 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 6 | 7 | const helpers = require('./helpers'); 8 | 9 | const NODE_ENV = process.env.NODE_ENV; 10 | const isProd = NODE_ENV === 'production'; 11 | 12 | module.exports = { 13 | entry: { 14 | 'app': [ 15 | helpers.root('client/app/index.js') 16 | ] 17 | }, 18 | 19 | output: { 20 | path: helpers.root('dist'), 21 | publicPath: '/' 22 | }, 23 | 24 | resolve: { 25 | extensions: ['.js', '.json', '.css', '.scss', '.html'], 26 | alias: { 27 | 'app': 'client/app' 28 | } 29 | }, 30 | 31 | module: { 32 | rules: [ 33 | // JS files 34 | { 35 | test: /\.jsx?$/, 36 | include: helpers.root('client'), 37 | loader: 'babel-loader' 38 | }, 39 | 40 | { 41 | // Preprocess our own .scss files 42 | test: /\.scss$/, 43 | exclude: /node_modules/, 44 | use: ['style-loader', 'css-loader', 'sass-loader'], 45 | }, 46 | { 47 | // Preprocess 3rd party .css files located in node_modules 48 | test: /\.css$/, 49 | include: /node_modules/, 50 | use: ['style-loader', 'css-loader'], 51 | } 52 | ] 53 | }, 54 | 55 | plugins: [ 56 | new webpack.HotModuleReplacementPlugin(), 57 | 58 | new webpack.DefinePlugin({ 59 | 'process.env': { 60 | NODE_ENV: JSON.stringify(NODE_ENV) 61 | } 62 | }), 63 | 64 | new HtmlWebpackPlugin({ 65 | template: helpers.root('client/public/index.html'), 66 | inject: true 67 | }), 68 | 69 | new ExtractTextPlugin({ 70 | filename: 'css/[name].[hash].css', 71 | disable: !isProd 72 | }), 73 | 74 | new CopyWebpackPlugin([{ 75 | from: helpers.root('client/public') 76 | }]) 77 | ] 78 | }; 79 | -------------------------------------------------------------------------------- /config/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const merge = require('webpack-merge'); 3 | 4 | const commonConfig = require('./webpack.common'); 5 | 6 | module.exports = merge(commonConfig, { 7 | devtool: 'cheap-module-source-map', 8 | 9 | mode: 'development', 10 | 11 | entry: { 12 | 'app': [ 13 | 'webpack-hot-middleware/client?reload=true' 14 | ] 15 | }, 16 | 17 | output: { 18 | filename: 'js/[name].js', 19 | chunkFilename: '[id].chunk.js' 20 | }, 21 | 22 | devServer: { 23 | contentBase: './client/public', 24 | historyApiFallback: true, 25 | stats: 'minimal' // none (or false), errors-only, minimal, normal (or true) and verbose 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /config/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const merge = require('webpack-merge'); 3 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin') 4 | 5 | const helpers = require('./helpers'); 6 | const commonConfig = require('./webpack.common'); 7 | 8 | module.exports = merge(commonConfig, { 9 | mode: 'production', 10 | 11 | output: { 12 | filename: 'js/[name].[hash].js', 13 | chunkFilename: '[id].[hash].chunk.js' 14 | }, 15 | 16 | plugins: [ 17 | new UglifyJsPlugin({ 18 | uglifyOptions: { 19 | mangle: true, 20 | sourcemap: false, 21 | compressor: { 22 | warnings: false, 23 | screw_ie8: true 24 | }, 25 | output: { 26 | comments: false 27 | } 28 | } 29 | }) 30 | ] 31 | }); 32 | -------------------------------------------------------------------------------- /misc/README.md: -------------------------------------------------------------------------------- 1 | # Information on miscellaneous files 2 | 3 | ## 1. React-table theming 4 | Some React-table elements don't get themed properly due to inconstitency in their color notation. Hence, I've fixed these issues and attached a replacement file named `react-table.css`. 5 | File to replace: `node_modules\react-table\react-table.css` -------------------------------------------------------------------------------- /misc/react-table.css: -------------------------------------------------------------------------------- 1 | .ReactTable{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;border:1px solid rgba(0,0,0,0.1);}.ReactTable *{box-sizing:border-box}.ReactTable .rt-table{-webkit-box-flex:1;-ms-flex:auto 1;flex:auto 1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch;width:100%;border-collapse:collapse;overflow:auto}.ReactTable .rt-thead{-webkit-box-flex:1;-ms-flex:1 0 auto;flex:1 0 auto;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;}.ReactTable .rt-thead.-headerGroups{background:rgba(0,0,0,0.03);border-bottom:1px solid rgba(0,0,0,0.05)}.ReactTable .rt-thead.-filters{border-bottom:1px solid rgba(0,0,0,0.05);}.ReactTable .rt-thead.-filters input,.ReactTable .rt-thead.-filters select{border:1px solid rgba(0,0,0,0.1);background:rgb(255,255,255);padding:5px 7px;font-size:inherit;border-radius:3px;font-weight:normal;outline:none}.ReactTable .rt-thead.-filters .rt-th{border-right:1px solid rgba(0,0,0,0.02)}.ReactTable .rt-thead.-header{box-shadow:0 2px 15px 0 rgba(0,0,0,0.15)}.ReactTable .rt-thead .rt-tr{text-align:center}.ReactTable .rt-thead .rt-th,.ReactTable .rt-thead .rt-td{padding:5px 5px;line-height:normal;position:relative;border-right:1px solid rgba(0,0,0,0.05);transition:box-shadow .3s cubic-bezier(.175,.885,.32,1.275);box-shadow:inset 0 0 0 0 transparent;}.ReactTable .rt-thead .rt-th.-sort-asc,.ReactTable .rt-thead .rt-td.-sort-asc{box-shadow:inset 0 3px 0 0 rgba(0,0,0,0.6)}.ReactTable .rt-thead .rt-th.-sort-desc,.ReactTable .rt-thead .rt-td.-sort-desc{box-shadow:inset 0 -3px 0 0 rgba(0,0,0,0.6)}.ReactTable .rt-thead .rt-th.-cursor-pointer,.ReactTable .rt-thead .rt-td.-cursor-pointer{cursor:pointer}.ReactTable .rt-thead .rt-th:last-child,.ReactTable .rt-thead .rt-td:last-child{border-right:0}.ReactTable .rt-thead .rt-resizable-header{overflow:visible;}.ReactTable .rt-thead .rt-resizable-header:last-child{overflow:hidden}.ReactTable .rt-thead .rt-resizable-header-content{overflow:hidden;text-overflow:ellipsis}.ReactTable .rt-thead .rt-header-pivot{border-right-color:#f7f7f7}.ReactTable .rt-thead .rt-header-pivot:after,.ReactTable .rt-thead .rt-header-pivot:before{left:100%;top:50%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}.ReactTable .rt-thead .rt-header-pivot:after{border-color:rgba(255,255,255,0);border-left-color:rgb(255,255,255);border-width:8px;margin-top:-8px}.ReactTable .rt-thead .rt-header-pivot:before{border-color:rgba(102,102,102,0);border-left-color:#f7f7f7;border-width:10px;margin-top:-10px}.ReactTable .rt-tbody{-webkit-box-flex:99999;-ms-flex:99999 1 auto;flex:99999 1 auto;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;overflow:auto;}.ReactTable .rt-tbody .rt-tr-group{border-bottom:solid 1px rgba(0,0,0,0.05);}.ReactTable .rt-tbody .rt-tr-group:last-child{border-bottom:0}.ReactTable .rt-tbody .rt-td{border-right:1px solid rgba(0,0,0,0.02);}.ReactTable .rt-tbody .rt-td:last-child{border-right:0}.ReactTable .rt-tbody .rt-expandable{cursor:pointer;text-overflow:clip}.ReactTable .rt-tr-group{-webkit-box-flex:1;-ms-flex:1 0 auto;flex:1 0 auto;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch}.ReactTable .rt-tr{-webkit-box-flex:1;-ms-flex:1 0 auto;flex:1 0 auto;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex}.ReactTable .rt-th,.ReactTable .rt-td{-webkit-box-flex:1;-ms-flex:1 0 0px;flex:1 0 0;white-space:nowrap;text-overflow:ellipsis;padding:7px 5px;overflow:hidden;transition:.3s ease;transition-property:width,min-width,padding,opacity;}.ReactTable .rt-th.-hidden,.ReactTable .rt-td.-hidden{width:0 !important;min-width:0 !important;padding:0 !important;border:0 !important;opacity:0 !important}.ReactTable .rt-expander{display:inline-block;position:relative;margin:0;color:transparent;margin:0 10px;}.ReactTable .rt-expander:after{content:'';position:absolute;width:0;height:0;top:50%;left:50%;-webkit-transform:translate(-50%,-50%) rotate(-90deg);transform:translate(-50%,-50%) rotate(-90deg);border-left:5.04px solid transparent;border-right:5.04px solid transparent;border-top:7px solid rgba(0,0,0,0.8);transition:all .3s cubic-bezier(.175,.885,.32,1.275);cursor:pointer}.ReactTable .rt-expander.-open:after{-webkit-transform:translate(-50%,-50%) rotate(0);transform:translate(-50%,-50%) rotate(0)}.ReactTable .rt-resizer{display:inline-block;position:absolute;width:36px;top:0;bottom:0;right:-18px;cursor:col-resize;z-index:10}.ReactTable .rt-tfoot{-webkit-box-flex:1;-ms-flex:1 0 auto;flex:1 0 auto;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;box-shadow:0 0 15px 0 rgba(0,0,0,0.15);}.ReactTable .rt-tfoot .rt-td{border-right:1px solid rgba(0,0,0,0.05);}.ReactTable .rt-tfoot .rt-td:last-child{border-right:0}.ReactTable.-striped .rt-tr.-odd{background:rgba(0,0,0,0.03)}.ReactTable.-highlight .rt-tbody .rt-tr:not(.-padRow):hover{background:rgba(0,0,0,0.05)}.ReactTable .-pagination{z-index:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:3px;box-shadow:0 0 15px 0 rgba(0,0,0,0.1);border-top:2px solid rgba(0,0,0,0.1);}.ReactTable .-pagination input,.ReactTable .-pagination select{border:1px solid rgba(0,0,0,0.1);background:rgb(255,255,255);padding:5px 7px;font-size:inherit;border-radius:3px;font-weight:normal;outline:none}.ReactTable .-pagination .-btn{-webkit-appearance:none;-moz-appearance:none;appearance:none;display:block;width:100%;height:100%;border:0;border-radius:3px;padding:6px;font-size:1em;color:rgba(0,0,0,0.6);background:rgba(0,0,0,0.1);transition:all .1s ease;cursor:pointer;outline:none;}.ReactTable .-pagination .-btn[disabled]{opacity:.5;cursor:default}.ReactTable .-pagination .-btn:not([disabled]):hover{background:rgba(0,0,0,0.3);color:rgb(255,255,255)}.ReactTable .-pagination .-previous,.ReactTable .-pagination .-next{-webkit-box-flex:1;-ms-flex:1;flex:1;text-align:center}.ReactTable .-pagination .-center{-webkit-box-flex:1.5;-ms-flex:1.5;flex:1.5;text-align:center;margin-bottom:0;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-pack:distribute;justify-content:space-around}.ReactTable .-pagination .-pageInfo{display:inline-block;margin:3px 10px;white-space:nowrap}.ReactTable .-pagination .-pageJump{display:inline-block;}.ReactTable .-pagination .-pageJump input{width:70px;text-align:center}.ReactTable .-pagination .-pageSizeOptions{margin:3px 10px}.ReactTable .rt-noData{display:block;position:absolute;left:50%;top:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);background:rgba(255,255,255,0.8);transition:all .3s ease;z-index:1;pointer-events:none;padding:20px;color:rgba(0,0,0,0.5)}.ReactTable .-loading{display:block;position:absolute;left:0;right:0;top:0;bottom:0;background:rgba(255,255,255,0.8);transition:all .3s ease;z-index:-1;opacity:0;pointer-events:none;}.ReactTable .-loading > div{position:absolute;display:block;text-align:center;width:100%;top:50%;left:0;font-size:15px;color:rgba(0,0,0,0.6);-webkit-transform:translateY(-52%);transform:translateY(-52%);transition:all .3s cubic-bezier(.25,.46,.45,.94)}.ReactTable .-loading.-active{opacity:1;z-index:2;pointer-events:all;}.ReactTable .-loading.-active > div{-webkit-transform:translateY(50%);transform:translateY(50%)}.ReactTable .rt-resizing .rt-th,.ReactTable .rt-resizing .rt-td{transition:none !important;cursor:col-resize;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none} -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "alcoding-club", 3 | "version": "1.0.0", 4 | "description": "Data Analysis project", 5 | "author": "Various", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/aniketnk/alcoding-data-analysis.git" 9 | }, 10 | "license": "MIT", 11 | "private": true, 12 | "scripts": { 13 | "start": "webpack -p --progress --profile --colors && node server", 14 | "start:dev": "node server" 15 | }, 16 | "engines": { 17 | "node": ">=6" 18 | }, 19 | "dependencies": { 20 | "@babel/core": "^7.0.0-beta.42", 21 | "@babel/preset-env": "^7.0.0-beta.42", 22 | "@babel/preset-react": "^7.0.0-beta.42", 23 | "archiver": "^3.0.0", 24 | "autoprefixer": "^8.6.4", 25 | "axios": "^0.18.0", 26 | "babel-loader": "^8.0.0-beta.4", 27 | "bcryptjs": "^2.4.3", 28 | "body-parser": "^1.18.3", 29 | "bootstrap": "^4.1.3", 30 | "browser-locale": "^1.0.3", 31 | "connect-history-api-fallback": "^1.5.0", 32 | "copy-webpack-plugin": "^4.5.2", 33 | "css-loader": "^0.28.11", 34 | "express": "^4.16.3", 35 | "extract-text-webpack-plugin": "^4.0.0-beta.0", 36 | "html-webpack-plugin": "^3.1.0", 37 | "jsonwebtoken": "^8.3.0", 38 | "mongoose": "^5.1.7", 39 | "multer": "^1.3.1", 40 | "node-sass": "^4.9.0", 41 | "nodemailer": "^4.6.8", 42 | "nodemon": "^1.17.2", 43 | "postcss-loader": "^2.1.3", 44 | "prop-types": "^15.6.2", 45 | "proptypes": "^1.1.0", 46 | "qs": "^6.5.2", 47 | "react": "^16.2.0", 48 | "react-avatar": "^3.4.6", 49 | "react-dom": "^16.2.0", 50 | "react-hot-loader": "^4.0.0", 51 | "react-iframe": "^1.3.0", 52 | "react-loading": "^2.0.3", 53 | "react-redux": "^5.0.7", 54 | "react-router": "^4.2.0", 55 | "react-router-dom": "^4.2.2", 56 | "react-table": "^6.8.6", 57 | "react-toasts": "^2.0.14", 58 | "reactstrap": "^6.5.0", 59 | "redux": "^4.0.0", 60 | "redux-persist": "^5.10.0", 61 | "redux-thunk": "^2.3.0", 62 | "request": "^2.87.0", 63 | "sass-loader": "^6.0.7", 64 | "style-loader": "^0.20.3", 65 | "webpack": "^4.20.2", 66 | "webpack-cli": "^3.1.1", 67 | "webpack-dev-middleware": "^3.0.1", 68 | "webpack-hot-middleware": "^2.21.2", 69 | "webpack-merge": "^4.1.2", 70 | "whatwg-fetch": "^2.0.3" 71 | }, 72 | "devDependencies": { 73 | "@babel/plugin-transform-runtime": "^7.1.0", 74 | "@babel/runtime": "^7.1.2", 75 | "redux-devtools-extension": "^2.13.7" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const nodemon = require('nodemon'); 2 | const path = require('path'); 3 | 4 | nodemon({ 5 | execMap: { 6 | js: 'node' 7 | }, 8 | script: path.join(__dirname, 'server/server'), 9 | ignore: [], 10 | watch: process.env.NODE_ENV !== 'production' ? ['server/*'] : false, 11 | ext: 'js' 12 | }) 13 | .on('restart', function() { 14 | console.log('Server restarted!'); 15 | }) 16 | .once('exit', function () { 17 | console.log('Shutting down server'); 18 | process.exit(); 19 | }); 20 | -------------------------------------------------------------------------------- /server/middleware/Token.js: -------------------------------------------------------------------------------- 1 | var jwt = require('jsonwebtoken'); 2 | const UserSession = require('../models/UserSession'); 3 | const readFileSync = require('fs').readFileSync; 4 | var privateKey = readFileSync('server/sslcert/server.key', 'utf8'); //privatekey for jwt 5 | 6 | var verifyToken = function (req, res, next) { 7 | console.log("Verifying token."); 8 | try { 9 | // x-access-token preferred 10 | var token = req.body.token || req.query.token || req.headers['x-access-token'] || req.headers['authorization'].split(' ')[1]; 11 | } 12 | catch (err) { 13 | var token = undefined; 14 | } 15 | if (!token) 16 | return res.status(403).send({ auth: false, message: 'No token provided.' }); 17 | 18 | jwt.verify(token, privateKey, function (err, decoded) { 19 | if (err) { 20 | if (err.name == "TokenExpiredError") { 21 | console.log("Deleting token from db. Token doesn't exist"); 22 | // delete token from UserSession 23 | UserSession.findOneAndRemove({ 24 | token: token 25 | }, (err) => { 26 | if (err) { 27 | return res.status(500).send({ 28 | success: false, 29 | message: "Error: Server error" 30 | }); 31 | } 32 | }); 33 | } 34 | return res.status(401).send({ sucess: false, message:err }); 35 | } 36 | // save to request for use in other routes 37 | req.user_id = decoded.user_id; 38 | req.role = decoded.role; 39 | req.token = token; 40 | // Check log in status 41 | UserSession.findOne({ 42 | token: token 43 | }, null, (err, session) => { 44 | if (err) { 45 | return res.status(500).send({ 46 | success: false, 47 | message: "Error: Server error" 48 | }); 49 | } 50 | if (!session) { 51 | return res.status(401).send({ 52 | success: false, 53 | message: "Error: Invalid token." 54 | }); 55 | } 56 | next(); 57 | }); 58 | }); 59 | } 60 | 61 | var requireRole = function (role) { 62 | return function (req, res, next) { 63 | verifyToken(req, res, function () { 64 | if (req.role != role) { 65 | return res.status(403).send({ 66 | success: false, 67 | message: "Error: Forbidden request." 68 | }); 69 | } 70 | next(); 71 | }); 72 | } 73 | } 74 | 75 | var verifyUser = function (req, res, next) { 76 | verifyToken(req, res, function () { 77 | if (req.params.userID == req.user_id || req.role == "admin") { 78 | next(); 79 | } 80 | else 81 | return res.status(403).send({ 82 | success: false, 83 | message: 'Error: Forbidden request.' 84 | }); 85 | }); 86 | } 87 | 88 | // Use verifyToken to check if the token is valid 89 | // Use verifyUser for non-admins when they want to access their own data 90 | // Use requireRole for role specfic functions, for eg, instructors setting up assignments or admin signup 91 | 92 | module.exports = { verifyToken, requireRole, verifyUser }; -------------------------------------------------------------------------------- /server/middleware/fileStorage.js: -------------------------------------------------------------------------------- 1 | var multer = require('multer'); 2 | const User = require('../models/User'); 3 | const File = require('../models/Files'); 4 | var Assignment = require('../models/assignments/Assignment'); 5 | var fs = require("fs"); 6 | var path = require('path'); 7 | var homedir = require('os').homedir(); 8 | var archiver = require('archiver'); 9 | 10 | var diskStorage = function (dir) { 11 | var storage = multer.diskStorage({ 12 | destination: function (req, file, cb) { 13 | if (!fs.existsSync(dir)) { 14 | fs.mkdirSync(dir); 15 | } 16 | cb(null, dir); 17 | }, 18 | filename: function (req, file, cb) { 19 | cb(null, file.originalname); 20 | }, 21 | onError: function (err, next) { 22 | console.log('error', err); 23 | next(err); 24 | }, 25 | //TODO: Make better cryptic naming convention for files 26 | }); 27 | return multer({ storage: storage }); 28 | } 29 | 30 | var fileUpload = function (req, res, next) { 31 | File.find({ 32 | user_id: req.user_id, 33 | originalname: req.file.originalname 34 | }, function (err, files) { 35 | if (err) { 36 | return res.status(500).send({ 37 | success: false, 38 | message: "Error: Server error" 39 | }); 40 | } 41 | else { 42 | var uploadFile = new File(); 43 | 44 | uploadFile.originalname = req.file.originalname; 45 | uploadFile.encoding = req.file.encoding; 46 | uploadFile.mimetype = req.file.mimetype; 47 | uploadFile.destination = req.file.destination; 48 | uploadFile.filename = req.file.filename; 49 | uploadFile.size = req.file.size; 50 | uploadFile.user_id = req.user_id; 51 | 52 | uploadFile.save(function (err, file) { 53 | if (err) { 54 | return res.status(500).send({ 55 | success: false, 56 | message: 'Error: Server error' 57 | }); 58 | } 59 | console.log(file._id + " Added to DB."); 60 | req.fileID = file._id; 61 | User.findOneAndUpdate({ 62 | _id: req.user_id 63 | }, { 64 | $push: { files: file._id } 65 | }, { new: true }, function (err, user) { 66 | if (err) { 67 | return res.status(500).send({ 68 | success: false, 69 | message: 'Error: Server error' 70 | }); 71 | } 72 | else { 73 | console.log("File added to user " + user._id); 74 | } 75 | }); 76 | }); 77 | } 78 | }) 79 | next(); 80 | } 81 | 82 | var downloadFile = function (dir) { 83 | return function (req, res, next) { 84 | File.find({ 85 | _id: req.params.fileID 86 | }, function (err, files) { 87 | if (err) { 88 | return res.status(500).send({ 89 | success: false, 90 | message: "Error: server error" 91 | }); 92 | } 93 | if (files.length == 0) { 94 | return res.status(404).send({ 95 | success: false, 96 | message: "Error: No file found with this id" 97 | }); 98 | } 99 | User.findOne({ 100 | _id: req.params.userID 101 | }, function(err, user){ 102 | if(err){ 103 | return res.status(500).send({ 104 | success: false, 105 | message: "Error: server error" 106 | }); 107 | } 108 | if(!user){ 109 | return res.status(404).send({ 110 | success: false, 111 | message: "Error: No such user found" 112 | }); 113 | } 114 | var usn = user.usn 115 | var file = files[0]; 116 | var filePath = path.join(dir, file.originalname) 117 | var fileName = usn+'_'+file.originalname; 118 | return res.download(filePath, fileName, function (err) { 119 | if (err) { 120 | return res.status(404).send({ 121 | success: false, 122 | message: "Error: File not found." 123 | }); 124 | } 125 | }); 126 | }) 127 | }); 128 | } 129 | } 130 | 131 | var addFilesForZip = function(){ 132 | return function(req,res,next){ 133 | console.log("hello"); 134 | Assignment.findOne({ 135 | _id: req.params.assignmentID 136 | }, function(err, assignment){ 137 | if(err){ 138 | return res.status(500).send({ 139 | success: false, 140 | message: "Error: server error" 141 | }); 142 | } 143 | if(!assignment){ 144 | return res.status(404).send({ 145 | success: false, 146 | message: "Error: No such assignment found" 147 | }); 148 | } 149 | if(assignment.submissions.length>0){ 150 | var files = []; 151 | assignment.submissions.forEach(obj => { 152 | User.findOne({ 153 | _id: obj.user 154 | }, function(err, user){ 155 | if(err){ 156 | return res.status(500).send({ 157 | success: false, 158 | message: "Error: server error" 159 | }); 160 | } 161 | if(!user){ 162 | return res.status(404).send({ 163 | success: false, 164 | message: "Error: No such user found" 165 | }); 166 | } 167 | var usn = user.usn; 168 | File.findOne({ 169 | _id: obj.file 170 | }, function(err,file){ 171 | if(err){ 172 | return res.status(500).send({ 173 | success: false, 174 | message: "Error: server error" 175 | }); 176 | } 177 | if(!file){ 178 | return res.status(404).send({ 179 | success: false, 180 | message: "Error: No such file found" 181 | }); 182 | } 183 | var filePath = path.join(dir, file.originalname); 184 | var fileName = usn+'_'+file.originalname; 185 | var fileObj = {'path': filePath, 'name':fileName}; 186 | // fileObj['path'] = filePath; 187 | // fileObj['name'] = fileName; 188 | files.push(fileObj); 189 | req.filesZip = files; 190 | }); 191 | }) 192 | }) 193 | } 194 | }) 195 | next(); 196 | } 197 | } 198 | 199 | var zipFile = function(dir){ 200 | return function(req,res,next){ 201 | console.log(req) 202 | var archive = archiver('zip'); 203 | archive.on('error',function(err) { 204 | res.status(500).send({error: err.message}); 205 | }); 206 | res.on('close', function() { 207 | console.log('Archive wrote %d bytes', archive.pointer()); 208 | return res.status(200).send('OK').end(); 209 | }); 210 | var zipName = req.params.assignmentID+'.zip' 211 | res.attachment(zipName); 212 | archive.pipe(res); 213 | req.files.forEach(file => { 214 | archive.append(fs.createReadStream(file.path), {name: file.name}) 215 | }); 216 | archive.finalize(); 217 | } 218 | } 219 | 220 | //TODO: Delete file endpoint 221 | 222 | module.exports = { diskStorage, fileUpload, downloadFile, zipFile, addFilesForZip }; 223 | -------------------------------------------------------------------------------- /server/models/Assignments/Course.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | var Schema = mongoose.Schema; 3 | 4 | const CourseSchema = new mongoose.Schema({ 5 | class: { 6 | professor:{ 7 | type: Schema.Types.ObjectId, 8 | required: true 9 | }, 10 | sections: [{ 11 | type: String, 12 | required: true 13 | }] 14 | }, 15 | students: [{ 16 | type: Schema.Types.ObjectId, 17 | ref: 'User' 18 | }], 19 | name: { 20 | type: String, 21 | required: true, 22 | }, 23 | code: { 24 | type: String, 25 | required: true, 26 | }, 27 | department: { 28 | type: String, 29 | required: true, 30 | }, 31 | description: { 32 | type: String, 33 | }, 34 | anchorDescription: { 35 | type: String 36 | }, 37 | resourcesUrl: { 38 | type: String 39 | }, 40 | duration: { 41 | startDate: { 42 | type: Date, 43 | }, 44 | endDate: { 45 | type: Date, 46 | }, 47 | }, 48 | assignments: [{ 49 | type: Schema.Types.ObjectId, 50 | ref: 'Assignment' 51 | }], 52 | details: { 53 | credits: { 54 | type: Number, 55 | }, 56 | hours: { 57 | type: Number, 58 | }, 59 | isCore: { 60 | type: Boolean, 61 | default: true 62 | }, 63 | }, 64 | isDeleted: { 65 | type: Boolean, 66 | default: false 67 | } 68 | }); 69 | 70 | module.exports = mongoose.model('Course', CourseSchema); -------------------------------------------------------------------------------- /server/models/Files.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const FileSchema = new mongoose.Schema({ 4 | originalname: { 5 | type: String, 6 | required: true 7 | }, 8 | encoding: { 9 | type:String, 10 | required: true 11 | }, 12 | mimetype:{ 13 | type:String, 14 | required:true 15 | }, 16 | destination: { 17 | type: String, 18 | required: true 19 | }, 20 | filename: { 21 | type: String, 22 | required: true 23 | }, 24 | size: { 25 | type: Number, 26 | required: true 27 | }, 28 | user_id: { 29 | type: mongoose.Schema.Types.ObjectId, 30 | required:true 31 | } 32 | }, {strict:false}); 33 | 34 | module.exports = mongoose.model('File', FileSchema); 35 | -------------------------------------------------------------------------------- /server/models/Group.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const Schema = mongoose.Schema; 3 | 4 | const GroupSchema = new mongoose.Schema({ 5 | students:[{ 6 | type: Schema.Types.ObjectId, 7 | ref: 'User' 8 | }], 9 | name:{ 10 | type: String, 11 | required: true 12 | }, 13 | graduating:{ 14 | type: String, 15 | required: true 16 | }, 17 | isDeleted:{ 18 | type: Boolean, 19 | default: false 20 | } 21 | }) 22 | 23 | module.exports = mongoose.model('Group', GroupSchema); 24 | -------------------------------------------------------------------------------- /server/models/Template.js: -------------------------------------------------------------------------------- 1 | // const mongoose = require('mongoose'); 2 | 3 | // const CounterSchema = new mongoose.Schema({ 4 | // count: { 5 | // type: Number, 6 | // default: 0 7 | // } 8 | // }); 9 | 10 | // module.exports = mongoose.model('Counter', CounterSchema); 11 | 12 | // // Types - 13 | // // String 14 | // // Number 15 | // // Date 16 | // // Buffer 17 | // // Boolean 18 | // // Mixed 19 | // // ObjectId 20 | // // Array 21 | -------------------------------------------------------------------------------- /server/models/User.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const bcrypt = require('bcryptjs'); 3 | 4 | const UserSchema = new mongoose.Schema({ 5 | usn: { 6 | type: String, 7 | default: "", 8 | required: true 9 | }, 10 | password: { 11 | type: String, 12 | // required: true, 13 | default: "" 14 | }, 15 | name: { 16 | firstName: { 17 | type: String, 18 | default: "", 19 | required: true 20 | }, 21 | lastName: { 22 | type: String, 23 | default: "" 24 | } 25 | }, 26 | groups: [{ 27 | type: mongoose.Schema.Types.ObjectId, 28 | ref: 'Group' 29 | }], 30 | basicInfo: { 31 | // Contains mutable info 32 | email: { 33 | type: String, 34 | default: "" 35 | }, 36 | phone: { 37 | type: String, 38 | default: "" 39 | }, 40 | dob: { 41 | type: Date, 42 | default: Date.now() 43 | } 44 | }, 45 | contender: { 46 | // Contains mutable info 47 | rating: { 48 | type: Number, 49 | default: -1 50 | }, 51 | volatility: { 52 | type: Number, 53 | default: -1 54 | }, 55 | timesPlayed: { 56 | type: Number, 57 | default: -1 58 | }, 59 | lastFive: { 60 | type: Number, 61 | default: -1 62 | }, 63 | best: { 64 | type: Number, 65 | default: -1 66 | }, 67 | handles: { 68 | codechef: { 69 | type: String, 70 | default: "" 71 | }, 72 | codejam: { 73 | type: String, 74 | default: "" 75 | }, 76 | kickstart: { 77 | type: String, 78 | default: "" 79 | }, 80 | spoj: { 81 | type: String, 82 | default: "" 83 | }, 84 | hackerRank: { 85 | type: String, 86 | default: "" 87 | }, 88 | codeforces: { 89 | type: String, 90 | default: "" 91 | }, 92 | hackerEarth: { 93 | type: String, 94 | default: "" 95 | } 96 | } 97 | }, 98 | role: { 99 | type: String, 100 | default: "student" 101 | }, 102 | files: [mongoose.Schema.Types.ObjectId], 103 | isDeleted: { 104 | type: Boolean, 105 | default: false 106 | } 107 | }, { strict: false, timestamps: true }); 108 | 109 | // const saltRounds = 10; 110 | // UserSchema.methods = { 111 | // generateHash: function generateHash(plainTextPassword) { 112 | // var generatedHash = ""; 113 | // bcrypt.hash(plainTextPassword, saltRounds).then(function (hash) { 114 | // generatedHash = hash; 115 | // }); 116 | // console.log(plainTextPassword + "-->" + generatedHash); 117 | // return generatedHash; 118 | // }, 119 | // checkPassword: function (plainTextPassword) { 120 | // return bcrypt.compare(plainTextPassword, this.password).then(function (res) { 121 | // return res; 122 | // }); 123 | // } 124 | // }; 125 | 126 | // module.exports = mongoose.model('User', UserSchema); 127 | 128 | const saltRounds = 10; 129 | UserSchema.methods.generateHash = function (password) { 130 | return bcrypt.hashSync(password, bcrypt.genSaltSync(saltRounds), null); 131 | }; 132 | 133 | UserSchema.methods.checkPassword = function (plainTextPassword) { 134 | return bcrypt.compareSync(plainTextPassword, this.password); 135 | } 136 | 137 | module.exports = mongoose.model('User', UserSchema); 138 | -------------------------------------------------------------------------------- /server/models/UserSession.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const UserSessionSchema = new mongoose.Schema({ 4 | token: { 5 | type: String, 6 | required: true 7 | } 8 | }); 9 | 10 | module.exports = mongoose.model('UserSession', UserSessionSchema); -------------------------------------------------------------------------------- /server/models/assignments/Assignment.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | var Schema = mongoose.Schema; 3 | 4 | const AssignmentSchema = new mongoose.Schema({ 5 | course: { 6 | type: Schema.Types.ObjectId, 7 | ref: 'Course', 8 | required: true, 9 | }, 10 | name: { 11 | type: String, 12 | required: true, 13 | }, 14 | uniqueID: { 15 | type: String, 16 | required: true, 17 | }, 18 | type: { // MCQ, Quiz, Code 19 | type: String, 20 | required: true, 21 | }, 22 | details: { 23 | type: String, 24 | }, 25 | maxMarks: { 26 | type: Number, 27 | }, 28 | resourcesUrl: { 29 | type: String 30 | }, 31 | duration: { 32 | startDate: { 33 | type: Date, 34 | }, 35 | endDate: { 36 | type: Date, 37 | }, 38 | }, 39 | submissions: [{ 40 | user: { 41 | type: Schema.Types.ObjectId, 42 | ref: 'User', 43 | required: true 44 | }, 45 | file: { 46 | type: Schema.Types.ObjectId, 47 | ref: 'Files', 48 | }, 49 | marksObtained: { 50 | type: Number, 51 | default: -1 52 | } 53 | }], 54 | POC: { // Point Of Contact 55 | type: Schema.Types.ObjectId, 56 | ref: 'User', 57 | }, 58 | isDeleted: { 59 | type: Boolean, 60 | default: false 61 | } 62 | }); 63 | 64 | module.exports = mongoose.model('Assignment', AssignmentSchema); 65 | -------------------------------------------------------------------------------- /server/models/assignments/Course.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | var Schema = mongoose.Schema; 3 | 4 | const CourseSchema = new mongoose.Schema({ 5 | class: { 6 | professor:{ 7 | type: Schema.Types.ObjectId, 8 | required: true 9 | }, 10 | sections: [{ 11 | type: String, 12 | required: true 13 | }] 14 | }, 15 | students: [{ 16 | type: Schema.Types.ObjectId, 17 | ref: 'User' 18 | }], 19 | name: { 20 | type: String, 21 | required: true, 22 | }, 23 | code: { 24 | type: String, 25 | required: true, 26 | }, 27 | department: { 28 | type: String, 29 | required: true, 30 | }, 31 | description: { 32 | type: String, 33 | }, 34 | anchorDescription: { 35 | type: String 36 | }, 37 | resourcesUrl: { 38 | type: String 39 | }, 40 | duration: { 41 | startDate: { 42 | type: Date, 43 | }, 44 | endDate: { 45 | type: Date, 46 | }, 47 | }, 48 | assignments: [{ 49 | type: Schema.Types.ObjectId, 50 | ref: 'Assignment' 51 | }], 52 | details: { 53 | credits: { 54 | type: Number, 55 | }, 56 | hours: { 57 | type: Number, 58 | }, 59 | isCore: { 60 | type: Boolean, 61 | default: true 62 | }, 63 | }, 64 | isDeleted: { 65 | type: Boolean, 66 | default: false 67 | } 68 | }); 69 | 70 | module.exports = mongoose.model('Course', CourseSchema); -------------------------------------------------------------------------------- /server/models/contests/Contender.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | var Schema = mongoose.Schema; 3 | 4 | const ContenderSchema = new mongoose.Schema({ 5 | user: { 6 | type: Schema.Types.ObjectId, 7 | ref: 'User' 8 | }, 9 | handles: [{ 10 | platform: { 11 | type: String 12 | }, 13 | handle: { 14 | type: String 15 | } 16 | }], 17 | history: [{ 18 | contest: { 19 | type: Schema.Types.ObjectId, 20 | ref: 'Contest' 21 | }, 22 | score: { 23 | type: Number, 24 | }, 25 | rank: { 26 | type: Number, 27 | } 28 | }], 29 | currentRank: { 30 | type: Number, 31 | required: true, 32 | default: -1 33 | }, 34 | pastRanks: { 35 | type: [Number], 36 | }, 37 | isDeleted: { 38 | type: Boolean, 39 | default: false 40 | } 41 | }); 42 | 43 | module.exports = mongoose.model('Contender', ContenderSchema); 44 | -------------------------------------------------------------------------------- /server/models/contests/Contest.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | var Schema = mongoose.Schema; 3 | 4 | const ContestSchema = new mongoose.Schema({ 5 | name: { 6 | type: String, 7 | required: true 8 | }, 9 | platform: { 10 | type: String, 11 | required: true 12 | }, 13 | url: { 14 | type: String, 15 | required: true, 16 | }, 17 | ranksUrl: { 18 | type: String, 19 | required: true, 20 | }, 21 | date: { 22 | type: Date, 23 | required: true 24 | }, 25 | maxScore: { 26 | type: Number, 27 | }, 28 | contenders: { 29 | type: [Schema.Types.ObjectId], 30 | ref: 'Contender' 31 | }, 32 | isDeleted: { 33 | type: Boolean, 34 | default: false 35 | } 36 | }); 37 | 38 | module.exports = mongoose.model('Contest', ContestSchema); 39 | -------------------------------------------------------------------------------- /server/routes/api/admin.js: -------------------------------------------------------------------------------- 1 | const User = require('../../models/User'); 2 | const File = require('../../models/Files'); 3 | const Group = require('../../models/Group') 4 | var requireRole = require('../../middleware/Token').requireRole; 5 | // var fileDB = require('../../middleware/fileStorage').fileDB; 6 | var diskStorage = require('../../middleware/fileStorage').diskStorage; 7 | var fileUpload = require('../../middleware/fileStorage').fileUpload; 8 | var retrieveFile = require('../../middleware/fileStorage').retrieveFile; 9 | var fs = require("fs"); 10 | var dir = process.cwd() + '/../temp'; 11 | var keyName = "inputFile"; 12 | 13 | module.exports = (app) => { 14 | app.post('/api/admin/signup', requireRole("admin"), function (req, res) { 15 | 16 | var usn = req.body.usn; 17 | var firstName = req.body.firstName; 18 | var lastName = req.body.lastName; 19 | var email = req.body.email; 20 | var role = req.body.role; 21 | 22 | if (!firstName) { 23 | return res.status(400).send({ 24 | success: false, 25 | message: 'Error: First name cannot be blank.' 26 | }); 27 | } 28 | if (!usn) { 29 | return res.status(400).send({ 30 | success: false, 31 | message: 'Error: usn cannot be blank.' 32 | }); 33 | } 34 | 35 | // Process data 36 | usn = ('' + usn).toUpperCase().trim(); 37 | email = ('' + email).toLowerCase().trim(); 38 | 39 | // Deduplication flow 40 | User.find({ 41 | usn: usn 42 | }, (err, previousUsers) => { 43 | if (err) { 44 | return res.status(500).send({ 45 | success: false, 46 | message: 'Error: Server find error' 47 | }); 48 | } else if (previousUsers.length > 0) { 49 | return res.status(409).send({ 50 | success: false, 51 | message: 'Error: Account already exists.' 52 | }); 53 | } 54 | // Save the new user 55 | const newUser = new User(); 56 | 57 | newUser.usn = usn; 58 | newUser.name.firstName = firstName; 59 | if (lastName) { newUser.name.lastName = lastName; } 60 | if (email) { newUser.basicInfo.email = email; } 61 | newUser.password = newUser.generateHash(usn); 62 | 63 | if (role) { 64 | if (role == "admin") { 65 | return res.status(403).send({ 66 | success: false, 67 | message: "Error: Forbidden request, Cannot assign role:\"admin\"." 68 | }); 69 | } 70 | newUser.role = role; 71 | } 72 | newUser.save((err, user) => { 73 | if (err) { 74 | return res.status(500).send({ 75 | success: false, 76 | message: 'Error: Server error' 77 | }); 78 | } 79 | console.log(newUser._id + " Added to DB.") 80 | return res.status(200).send({ 81 | success: true, 82 | message: 'Signed up' 83 | }); 84 | }); 85 | }); 86 | }); // end of sign up endpoint 87 | 88 | app.post('/api/admin/upload', requireRole("admin"), diskStorage(dir).single(keyName), fileUpload, function (req, res) { 89 | if (!req.file) { 90 | return res.status(400).send({ 91 | success: false, 92 | message: "Error: File not recieved" 93 | }); 94 | } 95 | if (req.file) { 96 | return res.status(200).send({ 97 | success: true, 98 | message: "File uploaded and added to DB", 99 | data: req.file 100 | }); 101 | } 102 | }); 103 | 104 | app.delete('/api/admin/user', requireRole("admin"), function (req, res) { 105 | if (!req.body.usn) { 106 | return res.status(400).send({ 107 | success: false, 108 | message: "Error: usn not recieved" 109 | }); 110 | } 111 | User.findOneAndDelete({ 112 | usn: req.body.usn 113 | }, function (err, user) { 114 | if (err) { 115 | return res.status(500).send({ 116 | success: false, 117 | message: 'Error: Server error' 118 | }); 119 | } 120 | if (!user) { 121 | return res.status(404).send({ 122 | success: false, 123 | message: 'Error: User not found' 124 | }); 125 | } 126 | return res.status(200).send({ 127 | success: 'true', 128 | message: "User " + user._id + " successfully deleted" 129 | }) 130 | }) 131 | }); 132 | 133 | app.post('/api/admin/createGroup', requireRole('admin'), function (req, res) { 134 | if (!req.body.name) { 135 | return res.status(400).send({ 136 | success: false, 137 | message: "Error: name not recieved" 138 | }); 139 | } 140 | 141 | if (!req.body.usn) { 142 | return res.status(400).send({ 143 | success: false, 144 | message: "Error: usn not recieved" 145 | }); 146 | } 147 | 148 | if (!req.body.graduating) { 149 | return res.status(400).send({ 150 | success: false, 151 | message: "Error: graduating year not recieved" 152 | }); 153 | } 154 | 155 | User.findOne({ 156 | usn: req.body.usn, 157 | role: 'student', 158 | isDeleted: false 159 | }, function (err, user) { 160 | if (err) { 161 | return res.status(500).send({ 162 | success: false, 163 | message: 'Error: Server error' 164 | }); 165 | } 166 | if (!user) { 167 | return res.status(404).send({ 168 | success: false, 169 | message: 'Error: User not found' 170 | }); 171 | } 172 | 173 | var userID = user._id; 174 | Group.findOne({ 175 | name: req.body.name, 176 | graduating: req.body.graduating 177 | }, function (err, group) { 178 | if (err) { 179 | return res.status(500).send({ 180 | success: false, 181 | message: 'Error: Server error' 182 | }); 183 | } 184 | if (!group) { 185 | var newGroup = new Group(); 186 | newGroup.name = req.body.name; 187 | newGroup.graduating = req.body.graduating; 188 | newGroup.students = new Array(); 189 | newGroup.students.push(userID); 190 | newGroup.save(function (err, group) { 191 | if (err) { 192 | return res.status(500).send({ 193 | success: false, 194 | message: 'Error: Server error' 195 | }); 196 | } 197 | console.log("Group " + group._id + " added"); 198 | User.findOneAndUpdate({ 199 | _id: userID 200 | }, { 201 | $push: { "groups": group._id } 202 | }, { new: true }, function (err, user) { 203 | if (err) { 204 | return res.status(500).send({ 205 | success: false, 206 | message: 'Error: Server error' 207 | }); 208 | } 209 | return res.status(200).send({ 210 | success: true, 211 | message: "User added to Group " + group.name 212 | }) 213 | }) 214 | }) 215 | } 216 | else { 217 | Group.findOneAndUpdate({ 218 | _id: group._id, 219 | isDeleted: false 220 | }, { 221 | $push: { "students": userID } 222 | }, { new: true }, function (err, group) { 223 | if (err) { 224 | return res.status(500).send({ 225 | success: false, 226 | message: 'Error: Server error' 227 | }); 228 | } 229 | User.findOneAndUpdate({ 230 | _id: userID 231 | }, { 232 | $push: { "groups": group._id } 233 | }, { new: true }, function (err, user) { 234 | if (err) { 235 | return res.status(500).send({ 236 | success: false, 237 | message: 'Error: Server error' 238 | }); 239 | } 240 | return res.status(200).send({ 241 | success: true, 242 | message: "User added to Group " + group.name 243 | }) 244 | }) 245 | }) 246 | } 247 | }) 248 | }) 249 | }); 250 | } 251 | -------------------------------------------------------------------------------- /server/routes/api/contests.js: -------------------------------------------------------------------------------- 1 | const User = require('../../models/User'); 2 | const File = require('../../models/Files'); 3 | var requireRole = require('../../middleware/Token').requireRole; 4 | var verifyUser = require('../../middleware/Token').verifyUser; 5 | var path = require("path") 6 | var diskStorage = require('../../middleware/fileStorage').diskStorage; 7 | var fileUpload = require('../../middleware/fileStorage').fileUpload; 8 | var retrieveFile = require('../../middleware/fileStorage').retrieveFile; 9 | var fs = require("fs"); 10 | var dir = process.cwd() + '/../temp'; 11 | var keyName = "inputFile"; 12 | 13 | module.exports = (app) => { 14 | app.get('/api/contests/:userID/contenderInfo', function (req, res) { 15 | var userID = req.params.userID; 16 | if (!userID) { 17 | return res.status(400).send({ 18 | success: false, 19 | message: 'Error: userID not in parameters.' 20 | }); 21 | } 22 | 23 | User.find({ 24 | _id: userID, 25 | isDeleted: false 26 | }, (err, users) => { 27 | if (err) { 28 | return res.status(500).send({ 29 | success: false, 30 | message: "Error: Server error." 31 | }); 32 | } 33 | if (!users) { 34 | return res.status(404).send({ 35 | success: false, 36 | message: 'No users' 37 | }); 38 | } 39 | if (users.length != 1) { 40 | return res.status(404).send({ 41 | success: false, 42 | message: 'More than one user' 43 | }); 44 | } 45 | var user = users[0].toObject(); 46 | delete user.password; 47 | delete user.role; 48 | delete user.files; 49 | 50 | return res.status(200).send({ 51 | success: true, 52 | message: "Individual contender details retrieved.", 53 | contenderDetails: user 54 | }); 55 | 56 | }) 57 | }) 58 | 59 | app.get('/api/contests/updatedHandles', requireRole("admin"), function (req, res) { 60 | var file = path.join(dir, "handles.json"); 61 | 62 | User.find({ 63 | isDeleted: false, 64 | }, (err, users) => { 65 | if (err) { 66 | return res.status(500).send({ 67 | success: false, 68 | message: "Error: Server error." 69 | }); 70 | } 71 | if (!users) { 72 | return res.status(404).send({ 73 | success: false, 74 | message: 'No users' 75 | }); 76 | } 77 | var userContenderDetails = {}; 78 | for (var user of users) { 79 | let name = user.name.firstName + " " + user.name.lastName; 80 | delete user.contender["$init"]; 81 | userContenderDetails[user.usn] = Object.assign({}, user.contender); 82 | userContenderDetails[user.usn]["batch"] = user.batch; 83 | } 84 | 85 | let data = JSON.stringify(userContenderDetails, null, 2); 86 | // fs.writeFileSync(file, data); 87 | fs.writeFile(file, data, (err) => { 88 | if (err) { 89 | return res.status(500).send({ 90 | success: false, 91 | message: "Error: Server error." 92 | }); 93 | } 94 | return res.download(file, function (err) { 95 | if (err) { 96 | return res.status(404).send({ 97 | success: false, 98 | message: "Error: File not found." 99 | }); 100 | } 101 | }); 102 | }); 103 | }) 104 | }); 105 | 106 | app.get('/api/contests/globalRankList', function (req, res) { 107 | User.find({ 108 | isDeleted: false, 109 | }, (err, users) => { 110 | if (err) { 111 | return res.status(500).send({ 112 | success: false, 113 | message: "Error: Server error." 114 | }); 115 | } 116 | if (!users) { 117 | return res.status(404).send({ 118 | success: false, 119 | message: 'No users' 120 | }); 121 | } 122 | var userContenderDetails = []; 123 | for (var user of users) { 124 | var name = user.name.firstName + " " + user.name.lastName; 125 | pushObject = Object.assign({ usn: user.usn, name }, user.contender.toObject()); 126 | pushObject.rating = Math.round(pushObject.rating); 127 | pushObject.best = Math.round(pushObject.best); 128 | if (pushObject.rating != -1 && pushObject.timesPlayed != 0) 129 | userContenderDetails.push(pushObject); 130 | else 131 | continue; 132 | } 133 | return res.status(200).send({ 134 | success: true, 135 | message: "globalRankList retrieved.", 136 | globalRankList: { userContenderDetails } 137 | }); 138 | 139 | }) 140 | }) 141 | 142 | app.put('/api/contests/:userID/codingHandle', verifyUser, function (req, res) { 143 | var userID = req.params.userID; 144 | var codechef = req.body.codechef; 145 | var codejam = req.body.codejam; 146 | var kickstart = req.body.kickstart; 147 | var spoj = req.body.spoj; 148 | var hackerRank = req.body.hackerRank; 149 | var codeforces = req.body.codeforces; 150 | var hackerEarth = req.body.hackerEarth; 151 | 152 | // Deduplication flow 153 | User.find({ 154 | _id: userID, 155 | isDeleted: false 156 | }, (err, previousUsers) => { 157 | if (err) { 158 | return res.status(500).send({ 159 | success: false, 160 | message: 'Server find error' 161 | }); 162 | } 163 | else if (previousUsers.length > 0) { 164 | // Update 165 | var handles = previousUsers[0].contender.handles.toObject(); 166 | 167 | if (!handles || Array.isArray(handles)) 168 | handles = new Object(); 169 | if (codejam) handles.codejam = codejam; 170 | if (kickstart) handles.kickstart = kickstart; 171 | if (spoj) handles.spoj = spoj; 172 | if (hackerRank) handles.hackerRank = hackerRank; 173 | if (codeforces) handles.codeforces = codeforces; 174 | if (hackerEarth) handles.hackerEarth = hackerEarth; 175 | if (codechef) handles.codechef = codechef; 176 | 177 | previousUsers[0].contender.handles = handles; 178 | previousUsers[0].save((err, user) => { 179 | if (err) { 180 | console.log(err); 181 | return res.status(500).send({ 182 | success: false, 183 | message: 'Server error' 184 | }); 185 | } 186 | return res.status(200).send({ 187 | success: true, 188 | message: 'Contender Updated' 189 | }); 190 | }); 191 | } 192 | else { 193 | if (!usn) { 194 | return res.status(400).send({ 195 | success: false, 196 | message: 'Error: Could not find user' 197 | }); 198 | } 199 | } 200 | }); 201 | }); 202 | 203 | app.post('/api/contests/updateContenders', requireRole("admin"), function (req, res) { 204 | var usn = req.body.usn; 205 | var name = req.body.name; 206 | var email = req.body.email; 207 | var rating = req.body.rating; 208 | var volatility = req.body.volatility; 209 | var timesPlayed = req.body.timesPlayed; 210 | var lastFive = req.body.lastFive; 211 | var best = req.body.best; 212 | 213 | if (!usn) { 214 | return res.status(400).send({ 215 | success: false, 216 | message: 'Error: USN cannot be blank' 217 | }); 218 | } 219 | 220 | // Process data 221 | usn = ('' + usn).toUpperCase().trim(); 222 | 223 | // Deduplication flow 224 | User.find({ 225 | usn: usn, 226 | isDeleted: false 227 | }, (err, previousUsers) => { 228 | if (err) { 229 | return res.status(500).send({ 230 | success: false, 231 | message: 'Error: Server find error' 232 | }); 233 | } 234 | else if (previousUsers.length > 0) { 235 | // Update 236 | previousUsers[0].contender.rating = rating; 237 | previousUsers[0].contender.volatility = volatility; 238 | previousUsers[0].contender.timesPlayed = timesPlayed; 239 | previousUsers[0].contender.lastFive = lastFive; 240 | previousUsers[0].contender.best = best; 241 | 242 | previousUsers[0].save((err, user) => { 243 | if (err) { 244 | console.log(err); 245 | return res.status(500).send({ 246 | success: false, 247 | message: 'Error: Server error' 248 | }); 249 | } 250 | return res.status(200).send({ 251 | success: true, 252 | message: 'Contender Updated' 253 | }); 254 | }); 255 | } 256 | else { 257 | return res.status(400).send({ 258 | success: false, 259 | message: 'Error: User not found.' 260 | }); 261 | } 262 | }); 263 | }); 264 | } 265 | -------------------------------------------------------------------------------- /server/routes/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | module.exports = (app) => { 5 | // API routes 6 | fs.readdirSync(__dirname + '/api/').forEach((file) => { 7 | require(`./api/${file.substr(0, file.indexOf('.'))}`)(app); 8 | }); 9 | }; 10 | -------------------------------------------------------------------------------- /server/sendEmail/client_secret.json: -------------------------------------------------------------------------------- 1 | { 2 | "installed": { 3 | "client_id": "749126085914-e1va3c6rpqo01csl1b85i8o17fihhjh3.apps.googleusercontent.com", 4 | "project_id": "alcoding-220307", 5 | "auth_uri": "https://accounts.google.com/o/oauth2/auth", 6 | "token_uri": "https://www.googleapis.com/oauth2/v3/token", 7 | "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", 8 | "client_secret": "_gxBViW5P69LvKJx0w4kWo8o", 9 | "redirect_uris": [ 10 | "urn:ietf:wg:oauth:2.0:oob", 11 | "http://localhost" 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /server/sendEmail/forgotPassword.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Set up a new password for [Product Name] 7 | 8 | 9 | 10 | 28 | 29 | 30 | 31 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /server/sendEmail/mainMail.py: -------------------------------------------------------------------------------- 1 | from merge import SendMessage 2 | import os 3 | import datetime 4 | 5 | __location__ = os.path.realpath( 6 | os.path.join(os.getcwd(), os.path.dirname(__file__))) 7 | 8 | 9 | def mail(emailid, username, link): 10 | body = open(os.path.join(__location__, "forgotPassword.txt"), 'r').read() 11 | body = body.replace("{{username}}",username).replace("{{link}}",link) 12 | to = emailid 13 | sender = "alcodingclubportal@gmail.com" 14 | subject = "[The Alcoding Club] Password Reset" 15 | SendMessage(sender, to, subject, body, '') 16 | print(sender, to, ' at ', datetime.datetime.now(), "\n", sep = "\n") 17 | 18 | 19 | if __name__ == '__main__': 20 | try: 21 | fin = open(os.path.join(__location__, 'emails.csv'), 'r') 22 | lines = fin.readlines() 23 | except IOError: 24 | print("Error: File open error" + IOError) 25 | exit() 26 | 27 | for line in lines: 28 | emailid, username, link = line.split(",") 29 | mail(emailid, username, link) 30 | fdel = open(os.path.join(__location__, 'emails.csv'), 'w') 31 | fdel.seek(0) 32 | fdel.truncate() 33 | 34 | 35 | -------------------------------------------------------------------------------- /server/sendEmail/merge.py: -------------------------------------------------------------------------------- 1 | import httplib2 2 | import os 3 | import oauth2client 4 | from oauth2client import client, tools 5 | import base64 6 | from email.mime.multipart import MIMEMultipart 7 | from email.mime.text import MIMEText 8 | from apiclient import errors, discovery 9 | 10 | SCOPES = 'https://www.googleapis.com/auth/gmail.send' 11 | CLIENT_SECRET_FILE = 'client_secret.json' 12 | APPLICATION_NAME = 'Gmail API Python Send Email' 13 | 14 | def get_credentials(): 15 | home_dir = os.path.expanduser('~') 16 | credential_dir = os.path.join(home_dir, '.credentials') 17 | if not os.path.exists(credential_dir): 18 | os.makedirs(credential_dir) 19 | credential_path = os.path.join(credential_dir, 'gmail-python-email-send.json') 20 | store = oauth2client.file.Storage(credential_path) 21 | credentials = store.get() 22 | if not credentials or credentials.invalid: 23 | flow = client.flow_from_clientsecrets(CLIENT_SECRET_FILE, SCOPES) 24 | flow.user_agent = APPLICATION_NAME 25 | credentials = tools.run_flow(flow, store) 26 | print('Storing credentials to ' + credential_path) 27 | return credentials 28 | 29 | def SendMessage(sender, to, subject, msgHtml, msgPlain): 30 | credentials = get_credentials() 31 | http = credentials.authorize(httplib2.Http()) 32 | service = discovery.build('gmail', 'v1', http=http) 33 | message1 = CreateMessage(sender, to, subject, msgHtml, msgPlain) 34 | SendMessageInternal(service, "me", message1) 35 | 36 | def SendMessageInternal(service, user_id, message): 37 | try: 38 | message = (service.users().messages().send(userId=user_id, body=message).execute()) 39 | print('Message Id: %s' % message['id']) 40 | return message 41 | except errors.HttpError as error: 42 | print('An error occurred: %s' % error) 43 | 44 | def CreateMessage(sender, to, subject, msgHtml, msgPlain): 45 | msg = MIMEMultipart('alternative') 46 | msg['Subject'] = subject 47 | msg['From'] = sender 48 | msg['To'] = to 49 | # msg.attach(MIMEText(msgPlain, 'plain')) 50 | msg.attach(MIMEText(msgHtml, 'html')) 51 | raw = base64.urlsafe_b64encode(msg.as_bytes()) 52 | raw = raw.decode() 53 | body = {'raw': raw} 54 | return body 55 | -------------------------------------------------------------------------------- /server/sendEmail/run.sh: -------------------------------------------------------------------------------- 1 | python3 /home/alcoding/WEBAPP/server/sendEmail/mainMail.py >> /home/alcoding/WEBAPP/server/sendEmail/emailLogs.txt 2 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const fs = require('fs'); 3 | const historyApiFallback = require('connect-history-api-fallback'); 4 | const mongoose = require('mongoose'); 5 | const path = require('path'); 6 | const webpack = require('webpack'); 7 | const webpackDevMiddleware = require('webpack-dev-middleware'); 8 | const webpackHotMiddleware = require('webpack-hot-middleware'); 9 | 10 | var https = require('https'); 11 | 12 | const config = require('../config/config'); 13 | const webpackConfig = require('../webpack.config'); 14 | 15 | var privateKey = fs.readFileSync('server/sslcert/server.key', 'utf8'); 16 | var certificate = fs.readFileSync('server/sslcert/server.crt', 'utf8'); 17 | 18 | const isDev = process.env.NODE_ENV !== 'production'; 19 | const port = process.env.PORT || 8080; 20 | 21 | 22 | // Configuration 23 | // ================================================================================================ 24 | 25 | // Set up Mongoose 26 | mongoose.connect(isDev ? config.db_dev : config.db).then(() => { 27 | console.log("Connected to Database"); 28 | }).catch((err) => { 29 | console.log("Not Connected to Database ERROR! ", err); 30 | }); 31 | mongoose.Promise = global.Promise; 32 | 33 | // ExpressJS 34 | var app = express(); 35 | var bodyParser = require('body-parser'); 36 | app.use(bodyParser.json()); // support json encoded bodies 37 | app.use(bodyParser.urlencoded({ 38 | extended: true 39 | })); // support encoded bodies 40 | 41 | // CORS provision 42 | app.use(function (req, res, next) { 43 | res.header("Access-Control-Allow-Origin", "*"); 44 | res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); 45 | next(); 46 | }); 47 | 48 | // API routes 49 | require('./routes')(app); 50 | 51 | if (isDev) { 52 | // Dev Server 53 | const compiler = webpack(webpackConfig); 54 | 55 | app.use(historyApiFallback({ 56 | verbose: false 57 | })); 58 | 59 | app.use(webpackDevMiddleware(compiler, { 60 | publicPath: webpackConfig.output.publicPath, 61 | contentBase: path.resolve(__dirname, '../client/public'), 62 | stats: { 63 | colors: true, 64 | hash: false, 65 | timings: true, 66 | chunks: false, 67 | chunkModules: false, 68 | modules: false 69 | } 70 | })); 71 | 72 | app.use(webpackHotMiddleware(compiler)); 73 | app.use(express.static(path.resolve(__dirname, '../dist'))); 74 | } else { 75 | // Production Server 76 | app.use(express.static(path.resolve(__dirname, '../dist'))); 77 | app.get('*', function (req, res) { 78 | res.sendFile(path.resolve(__dirname, '../dist/index.html')); 79 | res.end(); 80 | }); 81 | } 82 | 83 | var credentials = { 84 | key: privateKey, 85 | cert: certificate 86 | }; 87 | var httpsServer = https.createServer(credentials, app); 88 | httpsServer.listen(8443); 89 | 90 | app.listen(port, '0.0.0.0', (err) => { 91 | if (err) { 92 | console.log(err); 93 | } 94 | 95 | console.info('>>> ?? Open http://0.0.0.0:%s/ in your browser.', port); 96 | }); 97 | 98 | module.exports = app; 99 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | switch (process.env.NODE_ENV) { 2 | case 'prod': 3 | case 'production': 4 | module.exports = require('./config/webpack.prod'); 5 | break; 6 | 7 | case 'dev': 8 | case 'development': 9 | default: 10 | module.exports = require('./config/webpack.dev'); 11 | } 12 | --------------------------------------------------------------------------------