def post(self): """ Pushes a new job to be tracked under a specific board owned by a user Parameters: - user_id - board_id - job_to_track """ printColoured(" * Tracking a new job", colour="yellow") request_params = dict(request.get_json()) try: board_id = request_params["board_id"] user_id = request_params["user_id"] job_to_track = request_params["job_to_track"] except KeyError as err: raise InvalidUserInput( description="Missing mandatory fields: {}".format(err)) job_id = add_job(board_id, user_id, job_to_track) push_stat( board_id, { "timestamp": int(time.time() - OFFSET), "activity": "application", "job_id": job_id }) return job_to_track
def put(self): """ Updates a tracked job Parameters: - user_id - board_id - job_id - updated_job """ printColoured(" * Updating an existing tracked job", colour="yellow") requests_params = dict(request.get_json()) user_id = requests_params["user_id"] board_id = requests_params["board_id"] job_id = requests_params["job_id"] updated_job = requests_params["updated_job"] # Push stat and eliminate duplicates if they occur after pushing push_stat( board_id, { "timestamp": time.time() - OFFSET, "activity": updated_job["current_status"], "job_id": job_id }) eliminate_stat_duplicates(board_id, job_id, updated_job["current_status"]) return update_job(user_id, board_id, job_id, updated_job)
def pretty_print_dict(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_print_dict(value, indent+1) else: printColoured('\t' * (indent+1) + str(value), colour="blue")
def get(self): """ Fetches a list of all users on the platform Parameters: - Nothing so far. TODO """ printColoured(" * Getting a list of all user", colour="yellow") return get_users()
def post(self): printColoured(" * Logging in a user", colour="yellow") request_params = dict(request.form) email = request_params["email"] password = request_params["password"] user_id = login_user(email, password) return { "user_id": user_id, "token": "EMPTY", }
def delete(self): """ Removes a tracked job and all its associated data """ printColoured(" * Removing a tracked job") request_params = dict(request.form) user_id = request_params["user_id"] board_id = request_params["board_id"] job_id = request_params["job_id"] return delete_job(user_id, board_id, job_id)
def get(self): """ Gets the comments SENT TO a given user Parameters: - user_id """ printColoured(" * Fetching list of all comments for a user", colour="yellow") request_params = dict(request.args) user_id = request_params["user_id"] return get_comments(user_id)
def delete(self): """ Removes a vote for a given comment Parameters: - user_id - comment_id """ printColoured(" * Removing a vote for a comment", colour="yellow") request_params = dict(request.get_json()) user_id = request_params["user_id"] comment_id = request_params["comment_id"] clear_vote(user_id, comment_id)
def delete(self): """ Deletes a comment. Parameters: - comment_id """ printColoured(" * Deleting a comment", colour="yellow") request_params = dict(request.get_json()) comment_id = request_params["comment_id"] delete_comment(comment_id) return comment_id
def put(self): """ Sets a new comment for an existing comment document Parameters: - comment_id - new_comment """ printColoured(" * Updating a comment", colour="yellow") request_params = dict(request.get_json()) comment_id = request_params["comment_id"] new_comment = request_params["new_comment"] edit_comment(comment_id, new_comment) return new_comment
def login_handler(): printColoured(" * Attempting Google login", colour="yellow") # 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 = client.prepare_request_uri( authorization_endpoint, redirect_uri=REDIRECT_URI, scope=["openid", "email", "profile"], ) return redirect(request_uri)
def get_job_postings(location, query, results_per_page, page, sort_criteria): """ Gets job postings """ cj = CareerjetAPIClient("en_AU") jobs_json = cj.search({ "location": location, "keywords": query, "affid": "213e213hd12344552", "pagesize": results_per_page, "page": page, "sort": sort_criteria, "user_ip": "11.22.33.44", "url": "http://www.example.com/jobsearch", "user_agent": "Mozilla/5.0 (X11; Linux x86_64; rv:31.0) Gecko/20100101 Firefox/31.0" }) try: for each_job in jobs_json["jobs"]: # Strip all leading non-alphanumeric characters each_job["description"] = re.sub( "^[^A-Za-z0-9]*", "", each_job["description"]).capitalize() # Truncate duplicate whitespaces each_job["description"] = re.sub("\s+", " ", each_job["description"]) each_job["description"] = re.sub("<b>", "", each_job["description"]) each_job["description"] = re.sub("</b>", "", each_job["description"]) # Capitalise all words occurring after punctuation such as . or ! p = re.compile(r'(?<=[\.\?!]\s)(\w+)') each_job["description"] = p.sub( lambda match: match.group().capitalize(), each_job["description"]) except Exception as err: printColoured("Failed to fetch jobs '{}'. Error: {}".format( query, err), colour="red") raise InvalidUserInput( description="Failed to find jobs for '{}'".format(query)) return dict(jobs_json)
def get(self): """ Getting company info [using opencorporates or wikipedia] Parameters: - company - disable_jobs (optional param) """ # Filter for the jobs that actually belong to company_name printColoured(" * Fetching company details", colour="yellow") request_params = dict(request.args) company_name = request_params["company"] if company_name == "": # Returns nothing if given nothing return { "company_info": { "company_details": "", "company_name": company_name }, } company_details = get_company_details(company_name) if "disable_jobs" in request_params and request_params[ "disable_jobs"] == 'true': return { "company_info": { "company_details": company_details, "company_name": company_name }, } job_resp = get_job_postings("Sydney", company_name, 10, 1, "relevance") job_list = job_resp["jobs"] job_list = list( filter(lambda x: x["company"].lower() == company_name.lower(), job_list)) print(len(job_list)) return { "company_info": { "company_details": company_details, "company_name": company_name }, "jobs": [*job_list] }
def post(self): """ Upvotes or downvotes a given comment. Parameters: - user_id (the ID of the user attempting to vote the comment) - comment_id - increment_amount (negative values indicate decrement) """ printColoured(" * Voting a comment", colour="yellow") request_params = dict(request.get_json()) user_id = request_params["user_id"] comment_id = request_params["comment_id"] increment_amount = request_params["increment_amount"] # TODO: Check if the user is authorised to vote this comment` vote_comment(user_id, comment_id, increment_amount)
def get(self): """ Given the job post URL, scrapes the given URL and returns extracted fields and an HTML body to render Parameters: - job_post_url Returns: { post_details: "", fields: {} } """ printColoured(" * Getting job post details", colour="yellow") request_params = dict(request.args) url = request_params["url"] return get_content(url)
def post(self): """ Parameters: - sender_user_id - receiver_user_id - comment """ printColoured(" * Posting a comment", colour="yellow") request_params = dict(request.get_json()) sender_user_id = request_params["sender_user_id"] receiver_user_id = request_params["receiver_user_id"] comment = request_params["comment"] post_comment( sender_user_id, receiver_user_id, comment ) return "TODO: successful request data"
def post(self): printColoured(" * Registering a new user", colour="yellow") request_params = dict(request.form) try: username = request_params["username"] email = request_params["email"] password = request_params["password"] user_id = add_user( username, email, password, "https://t4.ftcdn.net/jpg/02/15/84/43/360_F_215844325_ttX9YiIIyeaR7Ne6EaLLjMAmy4GvPC69.jpg" ) return { "user_id": user_id, "token": "EMPTY", } except Exception as err: printColoured(err, colour="red") raise err
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 get_company_details(company): """ Params: - company (str) Returns: - company_description (str) """ wiki_wiki = Wikipedia('en') try: # try different methods for searching for the company until something good is returned page = wiki_wiki.page(company + " (company)") if not page.exists(): page = wiki_wiki.page(company) except Exception as err: printColoured(err, colour="red") raise InvalidUserInput( description="Connection timed out. Please try again later") company_data = page.text company_description = company_data.split("\n")[0] return company_description
def get(self): """ Parameters: - location (str) - query (str) - results_per_page (int) - page (int) - sort_criteria (str - "relevance", "date", "salary") Returns a JSON object: { jobs: [ { company, title, locations, url, date, description, salary, salary_type, salary_currency_code, salary_min, salary_max, site, }, ... ] } """ # Run `pydoc careerjet_api` to see documentation. printColoured(" * Getting a list of jobs") request_params = dict(request.args) pretty_print_dict(request_params) # TODO: Checking if all mandatory requests parameters have been sent through if not all( param in request_params and request_params for param in ["location", "query", "results_per_page", "page", "sort_criteria" ]): printColoured(" * Missing mandatory fields", colour="red") raise InvalidUserInput( description="Request parameter is missing mandatory fields") try: return get_job_postings(request.args.get("location"), request.args.get("query"), request.args.get("results_per_page"), request.args.get("page"), request.args.get("sort_criteria")) except Exception as err: printColoured( " * CareerJet API Client failed to fetch jobs. Error: {}". format(err), colour="red") raise InvalidUserInput( description="Couldn't find any jobs. Try a different query")
def get(self): """ Fetches user activity data for each date. Preprocesses it so that each date has a list of activities the user did. This makes it easy for the frontend to render graphically Parameters: - user_id - board_id - start time - end time """ printColoured(" * Getting user activity stats", colour="yellow") request_params = dict(request.args) user_id = request_params["user_id"] board_id = request_params["board_id"] stats = fetch_stats(user_id, board_id) # Sort timestamps into ascending order stats.sort(key=lambda x: x["timestamp"]) start_date = datetime.fromtimestamp(int(request_params["start_time"])) end_date = datetime.fromtimestamp(int(request_params["end_time"])) delta = end_date - start_date if (delta.days < 0): raise InvalidUserInput( description= "Invalid time range. Check that the start time is after end time" ) date_list = {} # METHOD 1 - iterating through the entire stats array for each day # takes longer but more confident that it is more accurate for i in range(delta.days + 2): day = start_date + timedelta(days=i) day = day.strftime('%d/%m/%Y') date_list[day] = [] for j in range(len(stats)): curr_day = datetime.fromtimestamp( stats[j]['timestamp']).strftime('%d/%m/%Y') if curr_day != day: continue date_list[day].append((stats[j]['activity'])) date_list[day].sort() # METHOD 2 - stop iterating through the stats array when dates no longer match # keeps track of last position in stats array so no need to iterate through days that have already been matched # takes shorter time but might be less accurate '''for i in range(delta.days + 2): day = (start_date + timedelta(days = i)).strftime('%d/%m/%Y') k = 0 for j in range(k, len(stats)): curr_day = datetime.fromtimestamp(stats[j]['timestamp']).strftime('%d/%m/%Y') if curr_day < day: continue elif curr_day > day: k = j break if day not in date_list.keys(): activity_list = [] activity_list.append((stats[j]['activity'])) activity_list.sort() date_list[day] = activity_list''' # For each day between start time and end time, return date_list
def print_pretty_json(struct, colour="yellow"): """ Pretty print a JSON-serialisable data structure """ printColoured(json.dumps(struct, indent=4, sort_keys=True), colour=colour)
def login_callback_handler(): printColoured( " * Entering Google login callback. Redirect URL: {}. Authorisation response: {}" .format(REDIRECT_URI, request.url), 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 = client.prepare_token_request( token_endpoint, authorization_response=substitute_https(request.url), redirect_url=REDIRECT_URI, code=code) token_response = requests.post( token_url, headers=headers, data=body, auth=(GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET), ) printColoured(" * Parsing tokens: {}".format(token_response), colour="yellow") # Parse the tokens! 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 = 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"): # unique_id = userinfo_response.json()["sub"] users_email = userinfo_response.json()["email"] picture = userinfo_response.json()["picture"] users_name = userinfo_response.json()["given_name"] else: return "User email not available or not verified by Google.", 400 # Register the user, or log them in # Workaround for Google auth: the callback in the Flask server redirects back # to the homepage and embeds the token and id in the URL like this: # /home/user_id/token # The token and ID are extracted and removed out of the URL and saved to the # client's cookies try: printColoured(" * Signing them up", colour="yellow") # FIXME: Temporary password new_user_id = add_user(users_name, users_email, "asdfasdf", picture) # FIXME: No token return redirect("{}/home?user_id={}&token={}".format( CLIENT_HOME_URL, new_user_id, "EMPTY TOKEN")) except Exception as err: printColoured(err, color="red") printColoured( " * Failed. They are probably an existing user: {}".format( users_email), colour="yellow") existing_user_id = login_user(users_email, "asdfasdf") # FIXME: No token return redirect("{}/home?user_id={}&token={}".format( CLIENT_HOME_URL, existing_user_id, "EMPTY TOKEN"))
from pathlib import Path from JobTracker.utils.colourisation import printColoured from JobTracker.utils.debug import print_env_variables from flask_pymongo import PyMongo from JobTracker.exceptions import error_handler from flask_cors import CORS import pymongo import os from os.path import join, dirname # Setting the environment variables: env_path = join(dirname(__file__), '.env') load_dotenv(dotenv_path=env_path) # Creating the Flask app instance printColoured(" * Initialising Flask application") app = Flask(__name__, static_url_path="/static") CORS(app) app.config["RESUME_UPLOAD_PATH"] = 'resumes' # ===== Debug and Testing ===== print_env_variables() # This must be set to 1 to bypass InsecureTransportError when testing locally # Source: https://stackoverflow.com/questions/27785375/testing-flask-oauthlib-locally-without-https/27785830 if os.getenv("ENV_TYPE") == "development": printColoured(" * Disabling secure transport for Google OAuth in development", colour="yellow") os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1' # ===== App Configuration =====
def print_env_variables(): """ Lists out critical environment variables present in this project """ printColoured(" * === Environment variables in .env ===", colour="red") printColoured(" * Google Client ID: {}".format(os.getenv("GOOGLE_CLIENT_ID")), colour="red") printColoured(" * Google Client Secret: {}".format(os.getenv("GOOGLE_CLIENT_SECRET")), colour="red") printColoured(" * MongoDB URI: {}".format(os.getenv("DB_URI")), colour="red") printColoured(" * Environment type: {}".format(os.getenv("ENV_TYPE")), colour="red") # These MUST match the URIs saved on https://console.cloud.google.com/ for this project printColoured(" * === Google Auth URIs ===", colour="red") printColoured(" * DEV_REQUEST_REDIRECT_URI: {}".format(os.getenv("DEV_REQUEST_REDIRECT_URI")), colour="red") printColoured(" * PROD_REQUEST_REDIRECT_URI: {}".format(os.getenv("PROD_REQUEST_REDIRECT_URI")), colour="red")