├── .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 |
207 |
208 |
Upload/Download Contender Details:
209 |
217 |
218 |
Create New Group:
219 |
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 |
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 |
Change Password
83 |
84 |
85 |
86 |
87 |
88 |
89 |
Change Password
90 | ×
91 |
92 |
93 |
94 |
117 |
118 | Confirm Password Change
119 | Close
120 |
121 |
122 |
123 |
124 |
125 |
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 | Enter USN:
83 |
91 |
92 |
93 | {!this.state.isLoading ? Send Email : }
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 |
97 |
98 | Contests
99 |
100 |
101 | Courses
102 |
103 |
104 | Assignments
105 |
106 |
107 |
108 |
109 |
110 |
111 | {displayName.split(" ", 1)[0]}
112 |
113 |
114 |
115 | Profile
116 |
117 |
118 |
119 | {this.state.navbarIsOpen? "Options":""}
120 |
121 |
122 |
123 | Contribute
124 |
125 |
126 |
127 | Logout
128 |
129 |
130 |
131 |
132 |
133 | );
134 |
135 | const guestLinks = (
136 |
137 |
138 |
151 |
152 |
153 | {this.state.navbarIsOpen ? "Options" : ""}
154 |
155 |
156 |
157 | Contribute
158 |
159 |
160 |
161 | Forgot Password
162 |
163 |
164 |
165 |
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 |
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 |
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 | {/*
window.open("/download/" + this.props.fileID)}> Download Submission */}
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 |
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 | {/*
*/}
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 |
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 |
Edit
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 | Save
74 | Cancel
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 |
Change Password
83 |
84 |
85 |
86 |
87 |
88 |
89 |
Change Password
90 | ×
91 |
92 |
93 |
94 |
126 |
127 | Confirm Password Change
128 | Close
129 |
130 |
131 |
132 |
133 |
134 |
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 | {/*
Confirm Changes */}
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 |
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 |
32 |
33 |
34 |
35 |
36 | The Alcoding Club
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | Hi {{username}},
48 | You recently requested to reset your password for your Alcoding Club Web Portal. Use the button below to reset it. This password reset is only valid for the next one hour.
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | If you did not request a password reset, please ignore this email or contact support if you have questions.
71 | Thanks,
72 | The Alcoding Club Team
73 |
74 |
75 |
76 |
77 | If you're having trouble with the button above, click on the URL below.
78 | alcoding.cs.pes.edu/resetPassword
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
101 |
102 |
103 |
104 |
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 |
--------------------------------------------------------------------------------