-
setup database following db/README.md
-
change .env to appropriate values.
-
Make sure that node version is 14 (latest stable).
-
Install all dependencies
npm install
-
Run dev server
npm run dev
- express-validator
- passport-local
- node-postgres
- sql-template-strings
- app-root-path
- multer for file upload
- Cross site cookie (third-party cookies) is problematic. I couldn't find any value of {sameSite, secure} for cookie configuration which works both in production (https) and development (http). That's why if you run
npm run start
or withNODE_ENV=production
environment, authentication will fail after successful login.
-
add
"start": "NODE_ENV=production node src/index.js",
in scripts of package.json. -
Heroku assigns arbitrary port to each application which is saved in
PORT
environment variable, so useprocess.env.PORT ||
beforeprocess.env.API_PORT
in src/index.js -
Heroku assigns DATABASE_URL environment variable appropriately, also we need to allow unauthorized ssl. So add
connectionString: process.env.DATABASE_URL, ssl: { rejectUnauthorized: false, }
to new Pool({...}) in src/config/pool/index.js -
Enable proxy by adding
app.set("trust proxy", 1);
in src/index.js -
Add FRONTEND_ROOT_URL in .env, add process.env.FRONTEND_ROOT_URL origin in corsOptions in src/index.js.
-
Enable cross site https cookies by adding the following to COOKIE_OPTIONS in src/utils/constants.js:
sameSite: process.env.NODE_ENV === "production" ? 'none' : 'lax', // must be 'none' to enable cross-site delivery
secure: process.env.NODE_ENV === "production", // must be explicitly specified if sameSite='none', denotes whether allow only https
-
Commit these changes.
-
Run the following if you haven't before in your current desktop session.
heroku login
heroku container:login
- Run the following only once.
APP=<HEROKU_APP_NAME>
FRONTEND_ROOT_URL=<FRONTEND_ROOT_URL>
heroku create $APP && heroku addons:create heroku-postgresql:hobby-dev && git remote add heroku git@heroku.com:$APP.git
heroku config:set -a $APP FRONTEND_ROOT_URL=$FRONTEND_ROOT_URL COOKIE_SECRET=$(openssl rand -base64 32) NODE_ENV=production
- Run the following everytime you wanna deploy
APP=<HEROKU_APP_NAME>
heroku container:push -a $APP web
heroku container:release -a $APP web
- Run the following everytime you wanna restore your DB from db/dump.sql.
APP=<HEROKU_APP_NAME>
heroku pg:reset -a $APP -c $APP
DATABASE_URL=$(heroku config:get -a $APP DATABASE_URL)
psql -f db/dump.sql $DATABASE_URL && echo "restored $DATABASE_URL from db/dump.sql"
run heroku ps:scale -a $APP web=0
to shut down the heroku app, run heroku ps:scale -a $APP web=1
for starting again.
-
/login
Request body:{ "email": String, "password" : String, }
-
/signup
Request body:{ "name": String, "email": String, "password": String, "confirmed_password": String, }
-
/mcq/find2/:examId
Gets the accociated mcq challenge related to this particular examId
Request body: does not require
Response body:{ "challengeId" : String, "difficulty" : String, "score" : Number, "time" : Number }
-
/mcq/find/:topicID
Randomly gets a mcq challenge under this topic.
Request body: does not require
Response body:{ "topicId": String, "topicName" : String, "challengeId" : String, "difficulty" : String, "score" : Number, "time" : Number }
-
/mcq/start/:challengeId
User starts a challenge with this challengeId. Timer starts as soon as user hits this endpoint.
Request body: does not require
Response body: Here,questions
object does not contain answers.{ "title": String, "challengeId" : String, "difficulty" : String, "score" : Number, "time" : Number. "questions": Object, "resultId": Number }
-
/mcq/submit/:resultId
User submits a quiz. If submission is proper and within time limit, it gets accepted. 5 seconds of wiggle room is kept.
Request body:{ "answers" : Array }
Response body: This time,
questions
object contains correct answers.{ "title": String, "challengeId" : String, "difficulty" : String, "score" : Number, "time" : Number. "questions": Object, "resultId": Number }
-
/mcq/start/:challengeId
Request body: does not require
User starts this challenge. Timer starts as soon as user hit this endpoint. -
/dual/invitations?userid=...&days=...
get list of dual notifications for a particilura user filtered by last n days.Request body: does not require
Response body: Javascript array ofjson
data[ { "exam_id" : Number, "challenger_id" : Number, "challenger_name" : String, "challengee_id" : Number, "challengee_name" : String, "topic_id" : Number, "topic_name" : String, "status" : String, "last_accessed" : Date }, ... ]
-
/dual/invite
Invite a user to a dual challenge on a particular topicRequest body:
json
data{ "challengerId" : Number, "challengeeId" : Number, "topicId" : Number, "challengeId" : Number, }
Response body:
json
data containing challenge_id{ "id" : Number }
-
/dual/archive
Archive allpending
outdated invitationsRequest body: does not require
Response body: does not require -
/dual/accept
Accept a user's invitation on a particular topicRequest body:
json
data{ "examId" : Number, }
Response body:
json
data containing challenge_id{ "id" : Number }
-
/dual/reject
Reject a user's invitation on a particular topicRequest body:
json
data{ "examId" : Number, }
Response body:
json
data containing challenge_id{ "id" : Number }
-
/dual/complete
Complete a dual challenge on a particular topicRequest body:
json
data{ "examId" : Number, }
Response body:
json
data containing challenge_id{ "id" : Number }
-
/dual/start?examId=...&challengeId=...
Request body: does not require
User starts this challenge. Timer starts as soon as user hit this endpoint. -
/dual/result?examId=...
Request body: does not require
Response body: Javascript Array(2) ofjson
data{ "problem_title" : String, "question_answer_set" : JSON, "participant_id" : Number, "participant_name" : String, "participant_mark" : Number, "participant_submission" : Array, }
-
/public/best/:userid[?problemid=][?topicid=]
get best score(s) for given user [and problem] [and topic] in chronological order. best score means: highest score, earliest submission time. Also return problem's max score (to compare with this user's score) and category. _topicid=0 means all topics. -
/public/bestscores/:userid[?problemid=][?topicid=]
similar to best, except it considers only coding challenges and sorted by recency. -
/public/activities/:userid[?topicid=]
get all challenge results of userid [for given topic] sorted by recency. topicid=0 means all topics. -
/public/rank[?userid=][?topicid=]
get rank of user(s) [for given topic]. topicid=0 means all topics. -
/public/solidusercount/:topicid
get number of users with non-zero score in topicid. topicid=0 means all topics. -
/public/activeusers
get currently active user lists
Request body: does not require
Response body: Javascript array ofjson
data[ { "userid" : Number, "name" : String, "score" : String }, ... ]
-
/logout
Request body: does not require -
This will be updated along with implementation.