Skip to content

Commit

Permalink
Release V1.b: Add login procedure, basic API call and main routes 🎉 (#15
Browse files Browse the repository at this point in the history
)

* Modify username by email and improve auth and register form

* Add activation mail with code

* Update email password to env.variable

* Improve readability of code, add active link in email

* Bug fix at check for account already activate

* Feature: add the login_required decorator to required routes (#9)

* Feature: add the error handling function (#10)

* Feature: Add password minimum requirements and checks (#11)

* Feature: add the API querying function and parsing request (#13)

* Maintenance: removing database from remote (#14)

Co-authored-by: HNTQ <[email protected]>
  • Loading branch information
joshuaVayer and HNTQ authored Jun 26, 2021
1 parent 8a3dc93 commit d0d5063
Show file tree
Hide file tree
Showing 18 changed files with 619 additions and 56 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,6 @@ dmypy.json
# Cython debug symbols
cython_debug/

# Database
application.db

1 change: 1 addition & 0 deletions .idea/Movie-Search-CS50.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,7 @@ then run application
```bash
flask run
```

## Database

Versions of the database are stored [here](https://drive.google.com/drive/folders/1HBSa8qETHNVWOOtPGPnlrb54g0_1akBr?usp=sharing). (Only accessible for authorized members)
55 changes: 55 additions & 0 deletions api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""
All API queries, parsing, and formatting functions are stored in this file
This project use the TMD API.
"""
import os
import requests

API_KEY = os.environ.get("API_KEY")


# Queries
def query_by_title(title):
"""Will look for all types"""
try:
url = f"https://api.themoviedb.org/3/search/multi?api_key={API_KEY}&query={title}"
response = requests.get(url)
response.raise_for_status()
except requests.RequestException:
return None

return response.json()


# Parse and formats
def parse_query_by_title(response):
""" Will return the data used in search movie for the query by title"""
parsed_response = {
"movies": [],
"series": []
}
if not response:
return response

def generic(r):
return {
"id": r["id"],
"poster_path": r["poster_path"],
"backdrop_path": r["backdrop_path"],
"overview": r["overview"],
"media_type": r["media_type"],
"vote_average": r["vote_average"]
}

for result in response["results"]:
if "movie" in result["media_type"]:
movie_dict = generic(result)
movie_dict["title"] = result["original_title"]
movie_dict["release_date"] = result["release_date"]
parsed_response["movies"].append(movie_dict)
elif "tv" in result["media_type"]:
series_dict = generic(result)
movie_dict["title"] = result["name"]
parsed_response["series"].append(series_dict)

return parsed_response
187 changes: 149 additions & 38 deletions app.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
from flask import Flask, redirect, render_template, request, session
from flask import Flask, redirect, render_template, request, session, url_for
from cs50 import SQL
from flask_session import Session
from tempfile import mkdtemp
from werkzeug.security import check_password_hash, generate_password_hash

from api import query_by_title, parse_query_by_title
from helpers import getCode, activationMail, login_required, handle_error, match_requirements


app = Flask(__name__)

# Ensure templates are auto-reloaded
Expand Down Expand Up @@ -34,22 +39,30 @@ def login():
# Forget any user_id
session.clear()
if request.method == "POST":

email = request.form.get("email")
password = request.form.get("password")

# Ensure username was submitted
if not request.form.get("username"):
# TODO apology "must provide username"
return render_template("register.html")
if not email:
message = "Must provide email"
return render_template("login.html", message=message)

# Ensure password was submitted
elif not request.form.get("password"):
#TODO apology "must provide password"
return render_template("register.html")
if not password:
message = "Must provide password"
return render_template("login.html", message=message)

# Query database for username
rows = db.execute("SELECT * FROM user WHERE username = ?", request.form.get("username"))
rows = db.execute("SELECT * FROM user WHERE email = ?", email.lower())

# Ensure username exists and password is correct
if len(rows) != 1 or not check_password_hash(rows[0]["hash"], request.form.get("password")):
return render_template("register.html")
# TODO Apology "invalid username and/or password", 403
if len(rows) != 1 or not check_password_hash(rows[0]["hash"], password):
message = "Invalid username and/or password"
return render_template("login.html", message=message)

if not rows[0]["active"]:
return redirect(url_for("activate", email=email.lower()))

# Remember which user has logged in
session["user_id"] = rows[0]["id"]
Expand All @@ -58,42 +71,124 @@ def login():
return redirect("/")
# User reached route via GET (as by clicking a link or via redirect)
else:
return render_template("login.html")
if request.args.get('message'):
message = request.args.get('message')
else:
message = ""
return render_template("login.html", message=message)

@app.route("/register", methods=["GET", "POST"])
def register():
"""Register user"""
if request.method == "POST":
# Ensure username was fill
if not request.form.get("username"):
return render_template("register.html")
#TODO return apology("must provide username", 400)

username = request.form.get("username")
email = request.form.get("email")
password = request.form.get("password")
confirmPassword = request.form.get("confirmation")

# Ensure username was submitted
if not username:
message = "Must provide username"
return render_template("register.html", message=message)

# Ensure Email was submitted
if not email:
message = "Must provide email"
return render_template("register.html", message=message)

# Ensure password was submitted
if not request.form.get("password"):
return render_template("register.html")
#TODO return apology("must provide password", 400)
if not password:
message = "Must provide password"
return render_template("register.html", message=message)

# Ensure password was confirmed
if not request.form.get("confirmation"):
return render_template("register.html")
#TODO return apology("must confirm password ", 400)
if not confirmPassword:
message = "Must confirm password"
return render_template("register.html", message=message)

if confirmPassword != password:
message = "Passwords do not match"
return render_template("register.html", message=message)

if not match_requirements(password, 10):
message = "Password do not match the minimum requirements"
return render_template("register.html", message=message)

if db.execute("SELECT * FROM user WHERE email = ?", email.lower()) != []:
message = "Email already used"
return render_template("register.html", message=message)
hash = generate_password_hash(password, method='pbkdf2:sha256', salt_length=8)
db.execute("INSERT INTO user (username, email, hash) VALUES(?, ?, ?)", username, email.lower(), hash)

#mailing
code = getCode(8)
activationMail(email, username, code)

#save activation code
userId = db.execute("SELECT id FROM user WHERE email = ?", email.lower())
db.execute("INSERT INTO activation (user_id, activation_code) VALUES(?, ?)", userId[0]["id"], code)
return redirect(url_for("activate", email=email.lower()))
else:
return render_template("register.html")

if request.form.get("confirmation") != request.form.get("password"):
return render_template("register.html")
#TODO return apology("passwords do not match", 400)
@app.route("/activate", methods=["GET","POST"])
def activate():
if request.method == "POST":

if db.execute("SELECT * FROM user WHERE username = ?", request.form.get("username")) != []:
return render_template("register.html")
#TODO return apology("user exist", 400)
email = request.form.get("email")
confirmCode = request.form.get("confirm")
# Ensure Email was submitted
if not email:
message = "Must provide email"
return render_template("activation.html", message=message)

db.execute("INSERT INTO user (username, hash) VALUES(?, ?)", request.form.get("username"),
generate_password_hash(request.form.get("password"), method='pbkdf2:sha256', salt_length=8))
return redirect("/login")
# Ensure password was submitted
if not confirmCode:
message = "Must provide confirmation code"
return render_template("activation.html", message=message)

rows = db.execute("SELECT * FROM activation WHERE user_id = (SELECT id FROM user WHERE email = ?)", email.lower())
user = db.execute("SELECT active FROM user WHERE email = ?", email.lower())


if len(user) == 1 and user[0]["active"] == 1:
message = "Account already activated"
return redirect(url_for("login", message=message))
if len(rows) != 1 or rows[0]["activation_code"] != confirmCode:
message = "Invalid Email or confirmation code"
return render_template("activation.html", message=message)
db.execute("DELETE FROM activation WHERE id =?",rows[0]["id"])
db.execute("UPDATE user SET active = true WHERE id = ?", rows[0]["user_id"])
message = "Account activated"
return redirect(url_for("login", message=message))
else:
return render_template("register.html")
code = ""
email = ""
if request.args.get('email'):
email = request.args.get('email')

if request.args.get('code'):
code = request.args.get('code')

if code and email:
rows = db.execute("SELECT * FROM activation WHERE user_id = (SELECT id FROM user WHERE email = ?)", email.lower())
user = db.execute("SELECT active FROM user WHERE email = ?", email.lower())
if len(user) == 1 and user[0]["active"] == 1:
message = "Account already activated"
return redirect(url_for("login", message=message))
if len(rows) != 1 or rows[0]["activation_code"] != code:
message = "Invalid Email or confirmation code"
return render_template("activation.html", message=message)
db.execute("DELETE FROM activation WHERE id =?", rows[0]["id"])
db.execute("UPDATE user SET active = true WHERE id = ?", rows[0]["user_id"])
message = "Account activated"
return redirect(url_for("login", message=message))
return render_template("activation.html", email=email, code=code)


@app.route("/logout")
@login_required
def logout():
"""Log user out"""
# Forget any user_id
Expand All @@ -102,17 +197,33 @@ def logout():
# Redirect user to login form
return redirect("/")

@app.route("/userProfil", methods=["GET", "POST"])
def userProfil():
return render_template("user_profil.html")
@app.route("/profil", methods=["GET", "POST"])
@login_required
def profil():
return render_template("profil.html")

@app.route("/parameters", methods=["GET", "POST"])
@login_required
def parameters():
return render_template("parameters.html")

@app.route("/results", methods=["GET", "POST"])
def results():
return render_template("results.html")
@app.route("/search")
def search():
"""Basic search by title, can take category filters"""
# Assignment and checks
title = request.args.get('title')
# filters = get_categories(request.args.get('filters'))

if not title:
return render_template("/search.html", error="Please submit a valid search")

# Corresponding Api request
query = query_by_title(title)
results = parse_query_by_title(query)

return render_template("search.html",
movies=results["movies"],
series=results["series"])

@app.route("/details", methods=["GET", "POST"])
def details():
Expand Down
Binary file removed application.db
Binary file not shown.
Loading

0 comments on commit d0d5063

Please sign in to comment.