Adding Grading Functionality
//public/js/practice.js
class Typing{
  constructor(practice){
    this.letters = practice;
    this.counter = Math.floor(Math.random() * (this.letters).length);
    this.usedCounter = this.counter;
    this.nextKey = this.letters[this.counter][0];
    this.pressedKey = 0;
    this.mistake = 0;
    this.scoreLetter = 0;
    this.numLetters = 0;
    this.setClock();
    this.setColor(this.nextKey);
  }

  setColor(nextKey){
    var rightShift = {'A':1,'S':1,'D':1,'F':1,'G':1,'Z':1,'X':1,'C':1,'V':1,'B':1,'Q':1,'W':1,'E':1,'R':1,'T':1,
                      '~':1,'!':1,'@':1,'#':1,'$':1,'%':1};
    var leftShift = {'H':1,'J':1,'K':1,'L':1,':':1,'"':1,'N':1,'M':1,'<':1,'>':1,'?':1,'Y':1,'U':1,'I':1,'O':1,'P':1,
                    '{':1,'}':1,'|':1,'^':1,'&':1,'*':1,'(':1,')':1,'_':1,'+':1};

    var keys = $(".keyboard-base").children().css({'color':'black'});
    
    for(var index in keys){
      var key = keys[index].innerHTML;

      if(key){
        var data = keys[index].getAttribute("data-l");
        if(key == "'"){
          data += '"';
        }

        if((nextKey == data[5]) || (nextKey == data[6])){
          keys[index].focus();
          $(keys[index]).css({'color':'teal'});

          if(this.nextKey in rightShift){
            $('.rightshift').css({'color':'teal'});
          }else if(this.nextKey in leftShift){
            $('.leftshift').css({'color':'teal'});
          }

          break;
        }
      }
    }
    
    $('#letter').html(this.letters[this.counter][2]);
  }

  checkKey(key){
    if(key == this.nextKey){
      this.scoreLetter += 1;
      while(true){
        this.counter = Math.floor(Math.random() * (this.letters).length);
        if(this.counter != this.usedCounter){
          this.usedCounter = this.counter
          break;
        }
      }

      this.pressedKey = this.nextKey;
      this.nextKey = this.letters[this.counter][0];
      this.setColor(this.nextKey);
    }else{
      document.getElementById('beep').play();
      $('#mistake span').html(this.toKhNum(++this.mistake));
    }

    $('#letters span').html(this.toKhNum(++this.numLetters))
  }

  toKhNum(number){
    const khNum = {'0':'០', '1':'១', '2':'២', '3':'៣', '4':'៤', '5':'៥', '6':'៦', '7':'៧', '8':'៨', '9':'៩'};
    var stringNum = number.toString();
    var khNumString = '';
   
    for(var i in stringNum){
      khNumString += khNum[stringNum[i]];
    }
   
    return khNumString;
  }

  setClock(){
    var second = 0;
    var minute = 0;
    var hour = 0;
    var minuteTest = 0;
    var callNextLevel = true;
    
    var clock = setInterval(() => {
      $('#timelapse .second').html(this.toKhNum(++second));
      if(second == 60){
        second = 0;
        $('#timelapse .minute').html(this.toKhNum(++minute));
      }

      if(minute == 60){
        minute = 0;
        ++minuteTest;
        $('#timelapse .hour').html(this.toKhNum(++hour));
      }

      if((minuteTest <= 2) && (this.scoreLetter >= 480) && (this.mistake <= 10))
        if(callNextLevel){
          this.updateLevel();
          callNextLevel = false;
        }

    }, 1000);
  }

  updateLevel(){
    $.post("login/update",
      function(data, status){
        if(status == "success"){
          var grade = typing.toKhNum(data.grade);
          $('#info').html(`សូម​អបអរ​សាទ​ដោយ​អ្នក​បាន​​ឆ្លង​ចូល​កំរឹត​ទី ${grade} ហើយ!!`);
          $('#level span').html(grade);
        }
     });
  }
}//end of class
#controllers/login.py
import config
from copy import deepcopy
from bottle import Bottle, template, request, response, redirect
from verify_email import verify_email
from models import userdb

class Login(Bottle):
  def __init__(self):
    super().__init__()
    self.get('/', callback=self.index)
    self.post('/user', callback=self.postUser)
    self.get('/logout', callback=self.logout)
    self.post('/update', callback=self.updateUser)

    self.userdb = userdb.Userdb()
    
  def index(self):
    kdict = deepcopy(config.kdict)
    kdict['blogTitle'] = "ចុះឈ្មោះ"
    return template('login', data=kdict)

  def postUser(self):
    kdict = deepcopy(config.kdict)
    username = request.forms.getunicode('fusername')
    password = request.forms.getunicode('fpassword')
    email = request.forms.getunicode('femail')

    checkEmail = verify_email(email)

    if checkEmail and username and password:
      result = self.userdb.checkUser(username, password, email)
      if result:
        response.set_cookie('logged-in', result[0], path='/', secret=kdict['secretKey'])
        redirect('/')
      else:
        result = self.userdb.checkUsername(username)
        if not result:
          response.set_cookie('logged-in', username, path='/', secret=kdict['secretKey'])
          self.userdb.insert(username, password, email, 1, False)
          redirect('/')
        else:
          kdict['message'] = 'ឈ្មោះ​អ្នក​ប្រើប្រាស់​នេះ​ត្រូវ​បាន​គេប្រើ​រួច​ហើយ​។'
          return template('login', data=kdict)
    else:
      if not checkEmail:
        kdict['message'] = 'Email របស់​លោក​អ្នក​មិនត្រឹមត្រូវ​ទេ។'
        return template('login', data=kdict)
      elif not (username or password):
        kdict['message'] = 'ត្រូវ​មាន​ឈ្មោះ​អ្នក​ប្រើប្រាស់​និង​ពាក្យ​សំងាត់​។'
        return template('login', data=kdict)

  def logout(self):
    kdict = deepcopy(config.kdict)
    username = request.get_cookie('logged-in', secret=kdict['secretKey'])
    if username:
      self.userdb.deleteUser(username)
      
    response.delete_cookie('logged-in', path='/', secret=kdict['secretKey'])
    redirect('/')

  def updateUser(self):
    kdict = deepcopy(config.kdict)
    username = request.get_cookie('logged-in', secret=kdict['secretKey'])
    if username:
      self.userdb.updateUser(username)
      grade = self.userdb.checkUsername(username)
      return {'grade':grade[2]}

GitHub: https://khmerweb-typing.herokuapp.com
Heroku: https://khmerweb-typing.herokuapp.com/