UnderCover/undercover/routes.py

255 lines
8.3 KiB
Python
Raw Normal View History

# Copyright Sage Vaillancourt 2021
import json
import os
2022-09-22 15:43:36 -04:00
import subprocess
import threading
import urllib.parse
from flask import (Blueprint, render_template, request, make_response, session, redirect, jsonify)
from wtforms import Form, SelectField, StringField, TextAreaField, validators
from email_validator import validate_email, EmailNotValidError
2021-07-23 23:09:08 -04:00
import undercover.db as db
import undercover.email as email
from undercover.pdf_builder import CLData
writing_blueprint = Blueprint('writing', __name__,)
class CLForm(Form):
letterName = SelectField(
'Letter Name:',
[validators.optional()],
choices=[(1, 'LETTER TITLE')]
)
username = StringField(
'Username:',
[validators.Length(min=4, max=99)],
default="Sage"
)
company = StringField(
'Company:',
[validators.Length(min=2, max=99)],
default="BananaCorp"
)
jobAndPronoun = StringField(
'Job and Pronoun (a/an):',
[validators.Length(min=4, max=99)],
2021-07-24 01:08:18 -04:00
default="a banana stocker"
)
skillTypes = StringField(
'Skill Type:',
[validators.Length(min=2, max=99)],
default="practical"
)
mySkills = StringField(
'My Skills:',
[validators.Length(min=2, max=99)],
2021-07-24 01:08:18 -04:00
default="stocking bananas"
)
closingText = TextAreaField(
'Closing Text:',
[validators.Length(min=2, max=99)],
default="I look forward to hearing from you"
)
body = TextAreaField(
'Body:',
[validators.Length(min=4, max=9999)],
2022-09-22 15:47:51 -04:00
default=(
"My name is {\\username}. I'm excited for the opportunity to work as "
"{\\jobAndPronoun} with your company. I think my {\\skillTypes} knowledge "
2022-09-22 16:58:12 -04:00
"of {\\mySkills} could contribute well to your team.\n\n"
2022-09-22 15:47:51 -04:00
"I am passionate about what I do, and I think we would work well together.\n\n"
"Thank you for your consideration."
)
)
def render_index(form=CLForm(), error=None, status=200, letter_errors=None):
return make_response(
render_template(
'writing.jinja2',
form=form,
username=session.get('username'),
error=error,
letter_errors=letter_errors,
), status)
@writing_blueprint.route('/login', methods=['POST', 'GET'])
def login():
if request.method == 'POST':
2022-09-23 15:14:24 -04:00
username = request.form['login']
if db.login(username, request.form['password']):
session['username'] = username
return redirect('/')
return render_index(error="Invalid username or password", status=401)
return '''
<form method="post">
<p><input type=text name=username></p>
<p><input type=password name=password></p>
<p><input type=submit value=Login></p>
</form>
'''
2022-09-23 15:12:07 -04:00
@writing_blueprint.route('/logout', methods=['POST', 'GET'])
def logout():
session.pop('username', None)
return redirect('/')
@writing_blueprint.route('/', methods=['GET'])
def index_get():
email_address = session.get('username')
form = CLForm()
if email_address:
user = db.get_user(email_address)
letters = db.get_user_letters(user.id)
if len(letters) > 0:
letter_names = [(i + 1, letter.title) for i, letter in enumerate(letters)]
form.letterName.choices = letter_names
# TODO: Load this data more dynamically
# I.e. use a dictionary instead of explicitly-defined fields
2022-09-24 17:59:38 -04:00
data = json.loads(letters[0].contents)
form.letterName.data = 1
form.company.data = data['company']
form.body.data = data['body']
form.closingText.data = data['closingText']
form.jobAndPronoun.data = data['jobAndPronoun']
form.mySkills.data = data['mySkills']
form.skillTypes.data = data['skillTypes']
2022-09-24 17:59:38 -04:00
form.username.data = data['username']
return render_index(form=form)
2022-09-23 18:59:42 -04:00
@writing_blueprint.route('/create_account', methods=['GET'])
2022-09-23 19:40:07 -04:00
def create_account_page():
2022-09-23 18:59:42 -04:00
return render_template('create_account.jinja2')
2022-09-23 19:40:07 -04:00
@writing_blueprint.route('/create_account', methods=['POST'])
def create_account():
email_address = request.form['login']
try:
validate_email(email_address, check_deliverability=True)
except EmailNotValidError as e:
return render_index(error=str(e), status=401)
if db.get_user(email_address):
return render_index(error="A user with that email already exists!", status=401)
db.add_user(email_address, request.form['password'])
session['username'] = email_address
2022-09-23 19:40:07 -04:00
return redirect('/')
@writing_blueprint.route('/reset', methods=['POST', 'GET'])
def reset_password():
if request.method == 'POST':
email_address = request.form.get('login')
existing_reset_id = request.form.get('reset_id')
if email_address:
reset_id = db.initiate_password_reset(email_address)
if reset_id:
if not email.send_password_reset(email_address, 'https://undercover.cafe/reset?id=' + str(reset_id)):
return render_index(error="Failed to send reset email. Please try again later.", status=500)
elif existing_reset_id:
# TODO: Eventually remove db entry whether or not link is clicked
new_password = request.form['password']
if not db.complete_reset(existing_reset_id, new_password):
return render_index(error="Password reset failed. Your reset link may have expired.", status=500)
# TODO: Log in?
return redirect('/')
query_reset_id = request.args.get('id')
# TODO: Add password validation
return f'''
<form formaction="/reset" method="post">
<p><input type=hidden name=reset_id value="{query_reset_id}"></p>
<label>New Password:</label>
<p><input type=password name=password></p>
<p><input type=submit value="Save Password"></p>
</form>
'''
@writing_blueprint.route('/dbtest', methods=['GET'])
def db_test_get():
response = make_response(db.get_user_letters(1)[0].contents, 200)
response.mimetype = "text/plain"
return response
@writing_blueprint.route('/update', methods=['POST'])
def update_get():
expected_token = os.environ['GITLAB_HOOK_TOKEN']
given_token = request.headers['X-Gitlab-Token']
event_type = request.headers['X-Gitlab-Event']
if expected_token == given_token and event_type == "Push Hook":
print("Update notification received.")
response = make_response("", 200)
response.mimetype = "text/plain"
2022-09-22 15:43:36 -04:00
threading.Timer(5, git_update, []).start()
return response
else:
return make_response("", 404)
2022-09-22 15:43:36 -04:00
def git_update():
script = os.environ['UPDATE_SCRIPT_PATH']
if not script:
return
subprocess.run(['bash', '-c', "test -f " + script + " && " + script])
@writing_blueprint.route('/', methods=['POST'])
def index_post():
form = CLForm(request.form)
if form.validate():
selected_letter = form.letterName.data or '1'
2021-07-23 23:09:08 -04:00
data = CLData(
selectedLetter=int(selected_letter) - 1,
2021-07-23 23:09:08 -04:00
username=form.username.data,
company=form.company.data,
jobAndPronoun=form.jobAndPronoun.data,
skillTypes=form.skillTypes.data,
mySkills=form.mySkills.data,
closingText=form.closingText.data,
2021-07-23 23:09:08 -04:00
body=form.body.data,
)
email_address = session.get('username')
if email_address:
user = db.get_user(email_address)
letters = db.get_user_letters(user.id)
2022-09-24 23:02:52 -04:00
letter_json = jsonify(data).get_data(True)
if len(letters) == 0:
db.add_letter(user.id, 'My Cover Letter', letter_json)
else:
letter = letters[data.selectedLetter]
# TODO: Support title editing
2022-09-24 23:02:52 -04:00
db.edit_letter(letter.id, letter.title, letter_json)
(resp, errors) = data.generate_pdf()
if errors:
resp = render_index(form=form, letter_errors=errors)
# Save entered data as cookies on user's machine
for pair in data.get_pairs():
resp.set_cookie(pair[0], urllib.parse.quote(pair[1]))
return resp
return render_index(form=form)