Skip to content

mwashief/iclp-api

Repository files navigation

iclp-api

How to run

  • 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
    

Documentations of some used npm packages

NOTES

  • 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 with NODE_ENV=production environment, authentication will fail after successful login.

heroku deployment

  • 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 use process.env.PORT || before process.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.

Endpoints:

  • /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 of json 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 topic

    Request body: json data

    {
        "challengerId" : Number,
        "challengeeId" : Number,
        "topicId" : Number,
        "challengeId" : Number,
    }

    Response body: json data containing challenge_id

    {
        "id" : Number
    }
  • /dual/archive Archive all pending outdated invitations

    Request body: does not require
    Response body: does not require

  • /dual/accept Accept a user's invitation on a particular topic

    Request body: json data

    {
        "examId" : Number,
    }

    Response body: json data containing challenge_id

    {
        "id" : Number
    }
  • /dual/reject Reject a user's invitation on a particular topic

    Request body: json data

    {
        "examId" : Number,
    }

    Response body: json data containing challenge_id

    {
        "id" : Number
    }
  • /dual/complete Complete a dual challenge on a particular topic

    Request 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) of json 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 of json data

    [
        {
            "userid" : Number,
            "name" : String,
            "score" : String
        },
        ...
    ]
  • /logout Request body: does not require

  • This will be updated along with implementation.