def profile_stats_push_handler(): """ Params: - user_id (str) - child_id (str) - course_id (str) - lesson_id (str) - time_on_completion (integer timestamp in seconds) - num_incorrect (int) - time_taken (float or int) """ request_data = dict(request.form) printColoured( " ➤ A child has just completed a lesson! Received some stats to commit:" ) pretty(request_data) # try: course_id = request_data["course_id"] lesson_id = request_data["lesson_id"] difficulty = get_lesson_difficulty(course_id, lesson_id) return jsonify( save_stats( { "course_id": course_id, "lesson_id": lesson_id, "num_incorrect": request_data["num_incorrect"], "time_taken": request_data["time_taken"], "time_on_completion": request_data["time_on_completion"], "difficulty": difficulty }, request_data["user_id"], request_data["child_id"]))
def save_stats(stats, parent_user_id, child_id): """ TODO pushes an object to db.users.children.statistics array """ parent = get_user(user_id=parent_user_id) target_child = [ child for child in parent["children"] if child["_id"] == child_id ][0] # new_stats = target_child["statistics"].copy() # new_stats.append(stats) db.users.update_one( { "_id": ObjectId(parent_user_id), "children": { "$elemMatch": { "_id": { "$eq": child_id } } } }, {"$push": { "children.$.statistics": stats }}) printColoured(" ➤ Successfully saved new performance stats!") return stats
def profile_data_fetch_handler(): """ Given a user_id and token, gets the target user's profile data """ user_id = request.args.get("user_id") profile_data = get_user(user_id=user_id) printColoured(" ➤ Fetched user profile of: {}".format(user_id)) return jsonify(profile_data)
def profile_stats_fetch_handler(): """ Given a user_id and token, get their children's associated stats """ user_id = request.args.get("user_id") printColoured(" ➤ Getting child stats: {}".format(user_id)) stats = get_stats(user_id) return jsonify(stats)
def pretty(d, indent=1): """ Pretty print a dictionary """ for key, value in d.items(): printColoured('\t' * indent + str(key) + ":", colour="yellow") if isinstance(value, dict): pretty(value, indent + 1) else: printColoured('\t' * (indent + 1) + str(value), colour="blue")
def db_users(): """ Querying the database and displaying results """ users = [user for user in get_all_users()] courses_lessons = get_courses_lessons() courses_all = [course for course in get_courses_all()] printColoured(courses_lessons) return render_template("database.html", users=users, courses_lessons=courses_lessons, courses_all=courses_all)
def save_user(user) -> str: """ Saves and returns the ID of the new user Returns: str: ID of the new user """ printColoured(" ➤ Saving new user: {}".format(user), colour="blue") # Builtin function vars() takes an object and constructs a dict. The _id key is then # removed to prevent colliding with MongoDB's generated ID document = vars(user) del document["_id"] return insert("users", document)
def get_lesson_difficulty(course_id: str, lesson_id: str): all_course = get_courses_full() try: target_course = [ course for course in all_course if course["courseId"] == course_id ][0] target_lesson = [lesson for lesson in target_course["lessons"]] printColoured(" ➤ Found '{}' lesson '{}'".format(course_id, lesson_id)) return target_lesson[ "difficulty"] if "difficulty" in target_lesson else 0.5 except: raise InvalidUserInput( description="Failed to find course '{}', lesson '{}'".format( course_id, lesson_id))
def google_login_handler(): """ When this route is hit, the user is directed to Google's authentication page where they will choose a Google account to log in with. """ # Find out what URL to hit for Google login google_provider_cfg = get_google_provider_cfg() authorization_endpoint = google_provider_cfg["authorization_endpoint"] # Use library to construct the request for Google login and provide # scopes that let you retrieve user's profile from Google request_uri = google_client.prepare_request_uri( authorization_endpoint, redirect_uri="https://127.0.0.1:5000/api/auth/google/login/callback", scope=["openid", "email", "profile"], ) printColoured("REDIRECTING NOW", colour="blue") printColoured("Request URI: {}".format(request_uri)) return redirect(request_uri)
def error_handler(err): """ Returns JSON containing details of the failure event including the status code, name and message. This JSON message is returned to the client everytime the route handlers encounter a problem, for example, invalid user input. """ response = err.get_response() try: printColoured(" ➤ Error: {} {}".format(err, err.description), colour="red") response.data = dumps({ "code": err.code, "name": "System Error", "message": err.description }) response.content_type = 'application/json' return response except: printColoured(" ➤ Error: {}".format(err), colour="red") response.data = dumps({"code": err.code, "name": "System Error"}) response.content_type = 'application/json' return response
def wipe_all_users(): """ Wipes all documents from the GalacticEd 'users' collection """ db.users.drop() printColoured(" ➤ DROPPED USERS", colour="red")
def print_pretty_json(struct, colour="yellow"): printColoured(json.dumps(struct, indent=4, sort_keys=True), colour=colour)
def db_wipe_users(): """ Wipes all users from the database (drops the 'users' named collection) """ printColoured("Wiping all users") wipe_all_users() return render_template("database.html")
def profile_stats_push_handler(): """ TODO: documentation Params: - user_id (str) - child_id (str) - course_id (str) """ try: user_id = request.args.get("user_id") child_id = request.args.get("child_id") category = request.args.get("category") printColoured(" ➤ Recommending a lesson from '{}' for {}".format( category, child_id)) curr_timestamp = floor(time.time()) week_prior_timestamp = curr_timestamp - ( 1 * 7 * 24 * 60 * 60) # TODO: this is a little dumb two_week_prior_timestamp = curr_timestamp - ( 2 * 7 * 24 * 60 * 60) # TODO: this is a little dumb zero_reference = 0 all_stats = get_stats_in_range(user_id, child_id, "shapes", zero_reference, curr_timestamp) last_week_stats = get_stats_in_range(user_id, child_id, "shapes", week_prior_timestamp, curr_timestamp) this_week_stats = get_stats_in_range(user_id, child_id, "shapes", two_week_prior_timestamp, curr_timestamp) print_pretty_json(all_stats) # print_pretty_json(last_week_stats) # print_pretty_json(this_week_stats) # Getting the average time taken, num_incorrect, difficulty: printColoured( "Performances: (avg time taken, avg num_incorrect, avg difficulty)", colour="blue") printColoured("-> Global", colour="blue") global_performance = stats_summarise(all_stats) print_pretty_json(global_performance) printColoured("-> Last Week", colour="blue") last_week_performance = stats_summarise(last_week_stats) print_pretty_json(last_week_performance) printColoured("-> This Week", colour="blue") this_week_performance = stats_summarise(this_week_stats) print_pretty_json(this_week_performance) except Exception as err: printColoured(err, colour="red") raise InvalidUserInput(description="Invalid or missing stats fields") return jsonify({"lesson-id": "shapes-4"})
from flask import Flask from flask_cors import CORS from dotenv import load_dotenv from pathlib import Path from GalacticEd.utils.colourisation import printColoured from flask_pymongo import PyMongo from GalacticEd.exceptions import error_handler from oauthlib.oauth2 import WebApplicationClient import pymongo import os # Setting the environment variables: # env_path = Path('.') / '.env.{}'.format(os.getenv("GALACTIC_ED_DEV_MODE")) env_path = Path('.') / '.env.{}'.format("development") load_dotenv(dotenv_path=env_path) printColoured( "Loaded the context of {} into the environment:".format(env_path)) # Creating the Flask app instance printColoured(" * Initialising Flask application") app = Flask(__name__) CORS(app) # ===== App Configuration ===== SECRET_KEY = os.getenv("SECRET_KEY") app.secret_key = SECRET_KEY GOOGLE_API_CLIENT_SECRET = os.getenv("GOOGLE_API_CLIENT_SECRET") GOOGLE_API_CLIENT_ID = os.getenv("GOOGLE_API_CLIENT_ID") GOOGLE_DISCOVERY_URL = ( "https://accounts.google.com/.well-known/openid-configuration")
def google_login_callback_handler(): """ This is the route hit by Google's API. The exact URL is specified in the develop console: https://console.developers.google.com/ """ printColoured("CLIENT_ID : {}, CLIENT_SECRET: {}".format( GOOGLE_API_CLIENT_ID, GOOGLE_API_CLIENT_SECRET), colour="yellow") # Get authorization code Google sent back to you code = request.args.get("code") # Find out what URL to hit to get tokens that allow you to ask for # things on behalf of a user google_provider_cfg = get_google_provider_cfg() token_endpoint = google_provider_cfg["token_endpoint"] # Prepare and send a request to get tokens! Yay tokens! token_url, headers, body = google_client.prepare_token_request( token_endpoint, authorization_response=request.url, redirect_url=request.base_url, code=code) token_response = requests.post( token_url, headers=headers, data=body, auth=(GOOGLE_API_CLIENT_ID, GOOGLE_API_CLIENT_SECRET), ) # Parse the tokens! google_client.parse_request_body_response(json.dumps( token_response.json())) # Now that you have tokens (yay) let's find and hit the URL # from Google that gives you the user's profile information, # including their Google profile image and email userinfo_endpoint = google_provider_cfg["userinfo_endpoint"] uri, headers, body = google_client.add_token(userinfo_endpoint) userinfo_response = requests.get(uri, headers=headers, data=body) # You want to make sure their email is verified. # The user authenticated with Google, authorized your # app, and now you've verified their email through Google! if userinfo_response.json().get("email_verified"): user_email = userinfo_response.json()["email"] user_image = userinfo_response.json()["picture"] user_name = userinfo_response.json()["given_name"] else: return "User email not available or not verified by Google.", 400 # Create a user in your db with the information provided # by Google printColoured( " ➤ GOOGLE API: Registered a user with details: name: {}, email: {}, image: {}" .format(user_name, user_email, user_image)) new_user = User(name=user_name, email=user_email, password="******") new_user.commit_user() printColoured("COMMITTED THE USER") # Send user back to homepage return """ <h3>You've logged in successfully!</h3> <div>Name: {}, Email: {}</div> <img src='{}'></img> """.format(user_name, user_email, user_image)
def wipe_user(email): """ TODO unprotected! This is just a convenience function """ printColoured(" ➤ Removing a user: {}".format(email), colour="yellow") db.users.remove({"email": email})