diff --git a/api/database.db b/api/database.db index 62b6344c..b00bd15e 100644 Binary files a/api/database.db and b/api/database.db differ diff --git a/api/databaseBACKUP.db b/api/databaseBACKUP.db deleted file mode 100644 index f309ea8b..00000000 Binary files a/api/databaseBACKUP.db and /dev/null differ diff --git a/api/databaseBACKUPNEW2.db b/api/databaseBACKUP22-11.db similarity index 76% rename from api/databaseBACKUPNEW2.db rename to api/databaseBACKUP22-11.db index 52e93219..62b6344c 100644 Binary files a/api/databaseBACKUPNEW2.db and b/api/databaseBACKUP22-11.db differ diff --git a/api/databaseBACKUPNEW.db b/api/databaseBACKUPNEW.db deleted file mode 100644 index fd3d667a..00000000 Binary files a/api/databaseBACKUPNEW.db and /dev/null differ diff --git a/api/models.py b/api/models.py index a7c7981b..6918a720 100644 --- a/api/models.py +++ b/api/models.py @@ -5,7 +5,7 @@ class MOVIE_DETAILS(db.Model): title = db.Column(db.String(50), nullable=False) release = db.Column(db.Integer, nullable=False) description = db.Column(db.String(500), nullable=False) - rating = db.Column(db.Integer, nullable=False) + rating = db.Column(db.Integer, nullable=False) #this class imports the MOVIE_DETAILS columns from the database movieLength = db.Column(db.String(50), nullable=False) category = db.Column(db.String(50), nullable=False) coverPhoto = db.Column(db.String(100), nullable=False) @@ -15,12 +15,12 @@ class MOVIE_DETAILS(db.Model): class USER_DETAILS(db.Model): id = db.Column(db.Integer, primary_key=True, unique=True, nullable=False, autoincrement=True) userName = db.Column(db.String(64), nullable=False) - userPassword = db.Column(db.String(64), nullable=False) + userPassword = db.Column(db.String(64), nullable=False) #this class imports the USER_DETAILS columns from the database joinDate = db.Column(db.String(64), nullable=False) class REVIEW_DETAILS(db.Model): reviewId = db.Column(db.Integer, primary_key=True, unique=True, nullable=False, autoincrement=True) - movieId = db.Column(db.Integer, db.ForeignKey('MOVIE_DETAILS.id'), nullable=False) + movieId = db.Column(db.Integer, db.ForeignKey('MOVIE_DETAILS.id'), nullable=False) #this class imports the REVIEW_DETAILS columns from the database userId = db.Column(db.Integer, db.ForeignKey('USER_DETAILS.id'), nullable=False) rating = db.Column(db.Integer, nullable=False) review = db.Column(db.String(500), nullable=False) diff --git a/api/views.py b/api/views.py index 5cc8a89a..b867cf1a 100644 --- a/api/views.py +++ b/api/views.py @@ -1,24 +1,37 @@ from flask import Blueprint, jsonify, request from . import db -from .models import MOVIE_DETAILS, USER_DETAILS, REVIEW_DETAILS +from .models import MOVIE_DETAILS, USER_DETAILS, REVIEW_DETAILS #the columns and the data from there are imported from flask_cors import CORS -import base64 +import base64 #used for decrypt hash main = Blueprint('main', __name__) CORS(main, supports_credentials=True) @main.route('/addreview', methods=['POST']) def addReview(): - movieID = request.get_json() - movieToReview = db.session.query((MOVIE_DETAILS.id),(MOVIE_DETAILS.category),(MOVIE_DETAILS.title),(MOVIE_DETAILS.coverPhoto),(MOVIE_DETAILS.rating),(MOVIE_DETAILS.longDescription),(MOVIE_DETAILS.release),(MOVIE_DETAILS.movieLength)).filter(MOVIE_DETAILS.id==movieID).all() - thisMovie = [] + movieID = request.get_json() #gets movieID from the page URL by using userParams function + movieToReview = db.session.query((MOVIE_DETAILS.id), + (MOVIE_DETAILS.title), + (MOVIE_DETAILS.release), + (MOVIE_DETAILS.description), + (MOVIE_DETAILS.category), + (MOVIE_DETAILS.movieLength), #gets the movie details to show on the page filtered down to the right movie + (MOVIE_DETAILS.coverPhoto), + (MOVIE_DETAILS.pagePhoto), + (MOVIE_DETAILS.longDescription), + (REVIEW_DETAILS.review), + (REVIEW_DETAILS.reviewDate), + (REVIEW_DETAILS.userId),db.func.avg(REVIEW_DETAILS.rating).label('averageRating'),).outerjoin(REVIEW_DETAILS).group_by(MOVIE_DETAILS.id).order_by(REVIEW_DETAILS.rating.desc()).filter(MOVIE_DETAILS.id==movieID).all() + - for movie in movieToReview: + thisMovie = [] #empty array created + + for movie in movieToReview: #loop through data thisMovie.append({ 'movieId' : movie.id, 'title': movie.title, - 'rating': movie.rating, + 'rating': movie.averageRating, 'category': movie.category, - 'coverPhoto': movie.coverPhoto, + 'coverPhoto': movie.coverPhoto, #appends the empty array with data from the query 'longDescription': movie.longDescription, 'release': movie.release, 'movieLength': movie.movieLength}) @@ -27,20 +40,20 @@ def addReview(): @main.route('/addnewreview', methods=['POST']) def addnewreview(): - details = request.get_json() + details = request.get_json() #gets the user input from add review form print(details) - newReview = REVIEW_DETAILS(rating=details[0],review=details[1],userId=details[2],movieId=details[3],reviewDate=details[4]) - db.session.add(newReview) - db.session.commit() - return jsonify () + newReview = REVIEW_DETAILS(rating=details[0],review=details[1],userId=details[2],movieId=details[3],reviewDate=details[4]) #assigns the input to the right columns in the database + db.session.add(newReview) #adds newReview to be commited + db.session.commit() #data is commited to the database + return jsonify () @main.route('/removereview', methods=['POST']) def removereview(): - details = request.get_json() + details = request.get_json() #gets the review data from myReviews page print(details) - REVIEW_DETAILS.query.filter(REVIEW_DETAILS.userId == details["userID"]).filter(REVIEW_DETAILS.movieId == details["movieId"]).delete() - db.session.commit() - return 'Done', 201 + REVIEW_DETAILS.query.filter(REVIEW_DETAILS.userId == details["userID"]).filter(REVIEW_DETAILS.movieId == details["movieId"]).delete() #the data is filtered down to get the intersection between the correct user and movieID + db.session.commit() #commit the delete + return 'Done', 201 #successful return @main.route('/movies') def movies(): @@ -49,7 +62,7 @@ def movies(): (MOVIE_DETAILS.release), (MOVIE_DETAILS.description), (MOVIE_DETAILS.category), - (MOVIE_DETAILS.movieLength), + (MOVIE_DETAILS.movieLength), #gets the movie details to show on the page grouped and joined to review datails to show ratings (MOVIE_DETAILS.coverPhoto), (MOVIE_DETAILS.pagePhoto), (MOVIE_DETAILS.longDescription), @@ -57,7 +70,7 @@ def movies(): (REVIEW_DETAILS.reviewDate), (REVIEW_DETAILS.userId),db.func.avg(REVIEW_DETAILS.rating).label('averageRating'),).outerjoin(REVIEW_DETAILS).group_by(MOVIE_DETAILS.id).order_by(REVIEW_DETAILS.rating.desc()).all() - movies = [] + movies = [] #empty array created for movie in movie_list: movies.append({ 'id' : movie.id, @@ -65,84 +78,82 @@ def movies(): 'release' : movie.release, 'description' : movie.description, 'rating' : movie.averageRating, - 'movieLength' : movie.movieLength, + 'movieLength' : movie.movieLength, #movies data added to the array 'category' : movie.category, 'coverPhoto' : movie.coverPhoto, 'pagePhoto' : movie.pagePhoto, 'longDescription' : movie.longDescription}) - return jsonify ({'movies': movies}) + return jsonify ({'movies': movies}) #movies data returned @main.route('/users') def users(): - user_list = USER_DETAILS.query.all() - users = [] + user_list = USER_DETAILS.query.all() #all user data is queried + users = [] #empty array created for user in user_list: users.append({ 'id' : user.id, - 'userName' : user.userName, + 'userName' : user.userName, #user data is added to the array 'userPassword' : user.userPassword}) - return jsonify ({'users': users}) + return jsonify ({'users': users}) #json object returned with details @main.route('/register', methods=['POST']) def addUser(): - details = request.get_json() - print(details) - check = db.session.query((USER_DETAILS.id)).filter(USER_DETAILS.userName==details[0]).all() - print(check) + details = request.get_json() #the details are taken from user input + check = db.session.query((USER_DETAILS.id)).filter(USER_DETAILS.userName==details[0]).all() #a check is ran to see if username already exists if len(check)==1: - return jsonify ({'userID': None}) - newUser = USER_DETAILS(userName=details[0],userPassword=details[1],joinDate=details[2]) + return jsonify ({'userID': None}) #if the username already exists the userID will be set to None so no new user will be added + newUser = USER_DETAILS(userName=details[0],userPassword=details[1],joinDate=details[2]) #new user details are added to the database db.session.add(newUser) - db.session.commit() - userID = db.session.query(USER_DETAILS.id).filter(USER_DETAILS.userName==details[0]).all() + db.session.commit() #data is commited to the database + userID = db.session.query(USER_DETAILS.id).filter(USER_DETAILS.userName==details[0]).all() #userID is queried from the database to get user logged in print(userID) - return jsonify ({'userID': userID[0][0]}) + return jsonify ({'userID': userID[0][0]}) #userID returns username and password @main.route('/login',methods=['GET']) def login(): - userID=None - token = request.headers.get('Authorization') + userID=None #userID is set to none to initialize it by default + token = request.headers.get('Authorization') #the authorisation headers are taken print(token) - [type,hash]=token.split() + [type,hash]=token.split() #authorisation token is split to get only the hash print(hash) - decryptedHash=base64.b64decode(hash.encode('ascii')) + decryptedHash=base64.b64decode(hash.encode('ascii')) #the has is decrypted using base64 print(decryptedHash) - decryptedHashArray=((decryptedHash).decode('utf-8')).split(':') + decryptedHashArray=((decryptedHash).decode('utf-8')).split(':') #the decrypted data is decoded to utf-8 and is split by the : symbol print(decryptedHashArray) - checkUserName = db.session.query((USER_DETAILS.id),(USER_DETAILS.userPassword)).filter(USER_DETAILS.userName==decryptedHashArray[0]).all() + checkUserName = db.session.query((USER_DETAILS.id),(USER_DETAILS.userPassword)).filter(USER_DETAILS.userName==decryptedHashArray[0]).all() #filter the userid and password if the username data equals to what the hash is print(checkUserName) if len(checkUserName)==1: - if checkUserName[0][1]==decryptedHashArray[1]: + if checkUserName[0][1]==decryptedHashArray[1]: #login details and database match checked userID=checkUserName[0][0] - return jsonify ({'userID': userID}) + return jsonify ({'userID': userID}) #userID is returned either with an existing one or None to either let the login happen or to stop it @main.route('/reviews') def reviews(): - review_list = REVIEW_DETAILS.query.all() - reviews = [] + review_list = REVIEW_DETAILS.query.all() #all of the reviews are taken from the database + reviews = [] #empty array created for review in review_list: reviews.append({ 'reviewId' : review.reviewId, 'movieId' : review.movieId, 'userId' : review.userId, - 'rating' : review.rating, + 'rating' : review.rating, #array appended with data from review table 'review' : review.review, 'reviewDate' : review.reviewDate}) - return jsonify ({'reviews': reviews}) + return jsonify ({'reviews': reviews}) #the reviews are returned @main.route('/recommended', methods=['POST']) def recommended(): - id = request.get_json() + id = request.get_json() #id is taken to show the correct user's data print(id) - recommended_list = db.session.query((MOVIE_DETAILS.category)).outerjoin(REVIEW_DETAILS).group_by(MOVIE_DETAILS.category).filter(REVIEW_DETAILS.userId==id).all() - moviesRecommended = [] + recommended_list = db.session.query((MOVIE_DETAILS.category)).outerjoin(REVIEW_DETAILS).group_by(MOVIE_DETAILS.category).filter(REVIEW_DETAILS.userId==id).all() #MOVIE_DETAILS and REVIEW_DETAILS joined and get data by the userId + moviesRecommended = [] #empty array created for movie in recommended_list: movie_list = db.session.query((MOVIE_DETAILS.id), @@ -151,7 +162,7 @@ def recommended(): (MOVIE_DETAILS.description), (MOVIE_DETAILS.category), (MOVIE_DETAILS.movieLength), - (MOVIE_DETAILS.coverPhoto), + (MOVIE_DETAILS.coverPhoto), #gets the movie details to show on the page grouped and joined to review datails to show ratings (MOVIE_DETAILS.pagePhoto), (MOVIE_DETAILS.longDescription), (REVIEW_DETAILS.rating), @@ -159,7 +170,7 @@ def recommended(): (REVIEW_DETAILS.reviewDate), (REVIEW_DETAILS.userId),db.func.avg(REVIEW_DETAILS.rating).label('averageRating'),).outerjoin(REVIEW_DETAILS).group_by(MOVIE_DETAILS.id).order_by(REVIEW_DETAILS.rating.desc()).filter(MOVIE_DETAILS.category==movie.category).all() - movies = [] + movies = [] #empty array created for movie2 in movie_list: movies.append({ 'id' : movie2.id, 'title' : movie2.title, @@ -167,51 +178,51 @@ def recommended(): 'description' : movie2.description, 'rating' : movie2.averageRating, 'movieLength' : movie2.movieLength, - 'category' : movie2.category, + 'category' : movie2.category, #appends data to the movies array 'coverPhoto' : movie2.coverPhoto, 'pagePhoto' : movie2.pagePhoto, 'longDescription' : movie2.longDescription}) - moviesRecommended.append({'category' : movie.category, 'movies' : movies}) + moviesRecommended.append({'category' : movie.category, 'movies' : movies}) #the category and all data from movies is appended to moviesRecommended - return jsonify ({'moviesRecommended': moviesRecommended}) + return jsonify ({'moviesRecommended': moviesRecommended}) #the moviesRecommended array is returned @main.route('/myReviews') def myReviews(): - userId=None + userId=None #userID is set to none to initialize it by default token = request.headers.get('Authorization') - [type,hash]=token.split() + [type,hash]=token.split() #authorisation token is split to get only the hash print(hash) - decryptedHash=base64.b64decode(hash.encode('ascii')) + decryptedHash=base64.b64decode(hash.encode('ascii')) #the has is decrypted using base64 print(decryptedHash) - decryptedHashArray=((decryptedHash).decode('utf-8')).split(':') - checkUserName = db.session.query((USER_DETAILS.id),(USER_DETAILS.userPassword)).filter(USER_DETAILS.userName==decryptedHashArray[0]).all() - userId = checkUserName[0][0] + decryptedHashArray=((decryptedHash).decode('utf-8')).split(':') #the decrypted data is decoded to utf-8 and is split by the : symbol + checkUserName = db.session.query((USER_DETAILS.id),(USER_DETAILS.userPassword)).filter(USER_DETAILS.userName==decryptedHashArray[0]).all() #filter the userid and password if the username data equals to what the hash is + userId = checkUserName[0][0] #userId equals to the first element in the array which is the id myReview_list = db.session.query((MOVIE_DETAILS.id), (MOVIE_DETAILS.category), (MOVIE_DETAILS.coverPhoto), - (REVIEW_DETAILS.rating).label('reviewRating'), + (REVIEW_DETAILS.rating).label('reviewRating'), #MOVIE_DETAILS and REVIEW_DETAILS are both used so they are joined and filtered so it only shows the reviews of the logged in user (REVIEW_DETAILS.review), (REVIEW_DETAILS.reviewDate), (REVIEW_DETAILS.userId)).outerjoin(REVIEW_DETAILS).group_by(MOVIE_DETAILS.id).filter(REVIEW_DETAILS.userId==userId).order_by(REVIEW_DETAILS.rating.desc()).all() - myreviews = [] + myreviews = [] #empty array created for review in myReview_list: averageRating = db.session.query((MOVIE_DETAILS.id),db.func.avg(REVIEW_DETAILS.rating).label('averageRating'),).outerjoin(REVIEW_DETAILS).group_by(MOVIE_DETAILS.id).order_by(REVIEW_DETAILS.rating.desc()).filter(REVIEW_DETAILS.movieId==review.id).all() - print(averageRating) + print(averageRating) #average rating is worked out using the .func.avg and has joined the MOVIE_DETAILS and REVIEW_DETAILS to do so using the REVIEW_DETAILS.movieId and review.id print(userId,review.userId) myreviews.append({ 'movieId' : review.id, - 'averageRating': averageRating[0][1], + 'averageRating': averageRating[0][1], #avarage rating is the second element in the array so second element is appended to array 'reviewRating' : review.reviewRating, - 'review' : review.review, + 'review' : review.review, #rest of the data is appanded to the array 'userId' : review.userId, 'reviewDate' : review.reviewDate, 'coverPhoto' : review.coverPhoto}) - return jsonify ({'myReviews': myreviews}) + return jsonify ({'myReviews': myreviews}) #array is returned @main.route('/categories', methods=['POST']) def categories(): - category = request.get_json() + category = request.get_json() #catergory is taken from useParams function print(category) movie_list = db.session.query((MOVIE_DETAILS.id), (MOVIE_DETAILS.title), @@ -219,7 +230,7 @@ def categories(): (MOVIE_DETAILS.description), (MOVIE_DETAILS.category), (MOVIE_DETAILS.movieLength), - (MOVIE_DETAILS.coverPhoto), + (MOVIE_DETAILS.coverPhoto), #data is queried to get MOVIE_DETAILS and REVIEW_DETAILS of specific category (MOVIE_DETAILS.pagePhoto), (MOVIE_DETAILS.longDescription), (REVIEW_DETAILS.rating), @@ -228,7 +239,7 @@ def categories(): (REVIEW_DETAILS.userId),db.func.avg(REVIEW_DETAILS.rating).label('averageRating'),).outerjoin(REVIEW_DETAILS).group_by(MOVIE_DETAILS.id).order_by(REVIEW_DETAILS.rating.desc()).filter(MOVIE_DETAILS.category==category).all() - movies = [] + movies = [] #empty array craeted print(movies) for movie in movie_list: @@ -237,17 +248,17 @@ def categories(): 'release' : movie.release, 'description' : movie.description, 'rating' : movie.averageRating, - 'movieLength' : movie.movieLength, + 'movieLength' : movie.movieLength, #data for a category is appeneded to the array 'category' : movie.category, 'coverPhoto' : movie.coverPhoto, 'pagePhoto' : movie.pagePhoto, 'longDescription' : movie.longDescription}) - return jsonify ({'categories': movies}) + return jsonify ({'categories': movies}) #movie data is returned @main.route('/search', methods=['POST']) def search(): - searchTerm = request.get_json() + searchTerm = request.get_json() #the term being searched by user is requested through the input on the page print(searchTerm) movie_list = db.session.query((MOVIE_DETAILS.id), (MOVIE_DETAILS.title), @@ -256,14 +267,14 @@ def search(): (MOVIE_DETAILS.category), (MOVIE_DETAILS.movieLength), (MOVIE_DETAILS.coverPhoto), - (MOVIE_DETAILS.pagePhoto), + (MOVIE_DETAILS.pagePhoto), #MOVIE_DETAILS and REVIEW_DETAILS are queried to show on the page using the .contains function to get all movies that contain the search term in the title (MOVIE_DETAILS.longDescription), (REVIEW_DETAILS.rating), (REVIEW_DETAILS.review), (REVIEW_DETAILS.reviewDate), (REVIEW_DETAILS.userId),db.func.avg(REVIEW_DETAILS.rating).label('averageRating'),).outerjoin(REVIEW_DETAILS).group_by(MOVIE_DETAILS.id).order_by(REVIEW_DETAILS.rating.desc()).filter(MOVIE_DETAILS.title.contains(searchTerm)).all() - movies = [] + movies = [] #empty array created print(movies) for movie in movie_list: @@ -272,17 +283,17 @@ def search(): 'release' : movie.release, 'description' : movie.description, 'rating' : movie.averageRating, - 'movieLength' : movie.movieLength, + 'movieLength' : movie.movieLength, #movie data appended to array 'category' : movie.category, 'coverPhoto' : movie.coverPhoto, 'pagePhoto' : movie.pagePhoto, 'longDescription' : movie.longDescription}) - return jsonify ({'searchResults': movies}) + return jsonify ({'searchResults': movies}) #search results are returned @main.route('/movieReviews', methods=['POST']) def movieReviews(): - id = request.get_json() + id = request.get_json() #id is requested to know which movie is being shown print(id) movie_list = db.session.query((MOVIE_DETAILS.id), (MOVIE_DETAILS.title), @@ -290,7 +301,7 @@ def movieReviews(): (MOVIE_DETAILS.description), (MOVIE_DETAILS.category), (MOVIE_DETAILS.movieLength), - (MOVIE_DETAILS.coverPhoto), + (MOVIE_DETAILS.coverPhoto), #MOVIE_DETAILS data is queried for the current movie id joined with REVIEW_DETAILS to work out average rating (MOVIE_DETAILS.pagePhoto), (MOVIE_DETAILS.longDescription), (REVIEW_DETAILS.rating), @@ -299,7 +310,7 @@ def movieReviews(): (REVIEW_DETAILS.userId),db.func.avg(REVIEW_DETAILS.rating).label('averageRating'),).outerjoin(REVIEW_DETAILS).group_by(MOVIE_DETAILS.id).order_by(REVIEW_DETAILS.rating.desc()).filter(MOVIE_DETAILS.id==id).all() - review_list = db.session.query((REVIEW_DETAILS.rating),(REVIEW_DETAILS.review),(REVIEW_DETAILS.reviewDate),(REVIEW_DETAILS.userId), + review_list = db.session.query((REVIEW_DETAILS.rating),(REVIEW_DETAILS.review),(REVIEW_DETAILS.reviewDate),(REVIEW_DETAILS.userId), #REVIEW_DETAILS are needed to show all reviews of current movie (REVIEW_DETAILS.reviewId),(REVIEW_DETAILS.movieId),(USER_DETAILS.userName)).outerjoin(USER_DETAILS).filter(REVIEW_DETAILS.movieId==id).all() movies = [] @@ -308,7 +319,7 @@ def movieReviews(): 'title' : movie.title, 'release' : movie.release, 'description' : movie.description, - 'rating' : movie.averageRating, + 'rating' : movie.averageRating, #movie data is appeneded to array 'movieLength' : movie.movieLength, 'category' : movie.category, 'coverPhoto' : movie.coverPhoto, @@ -321,23 +332,23 @@ def movieReviews(): reviews.append({ 'reviewId' : review.reviewId, 'movieId' : review.movieId, 'userId' : review.userId, - 'rating' : review.rating, + 'rating' : review.rating, # review data is appanded to array 'review' : review.review, 'reviewDate' : review.reviewDate, 'userName' : review.userName}) - return jsonify ({'movieDetails': movies,'movieReviews': reviews}) + return jsonify ({'movieDetails': movies,'movieReviews': reviews}) #both arrays are returned @main.route('/myaccount', methods=['POST']) def myaccount(): - userID = request.get_json() + userID = request.get_json() #userID is taken to know which user is logged in print(userID) - user_list = USER_DETAILS.query.filter(USER_DETAILS.id==userID).all() + user_list = USER_DETAILS.query.filter(USER_DETAILS.id==userID).all() #all user information is queried if userID matches id in the MOVIE_DETAILS table review_list = db.session.query((REVIEW_DETAILS.rating), (REVIEW_DETAILS.review), (REVIEW_DETAILS.reviewDate), - (REVIEW_DETAILS.userId), + (REVIEW_DETAILS.userId), #reviews of logged in user are queried (REVIEW_DETAILS.reviewId), (REVIEW_DETAILS.movieId), (USER_DETAILS.userName)).outerjoin(USER_DETAILS).filter(REVIEW_DETAILS.userId==userID).all() @@ -348,12 +359,12 @@ def myaccount(): reviews.append({ 'reviewId' : review.reviewId, 'movieId' : review.movieId, 'userId' : review.userId, - 'rating' : review.rating, + 'rating' : review.rating, #review data added to review array 'review' : review.review, 'reviewDate' : review.reviewDate, 'userName' : review.userName}) - movieID=(reviews[-1])['movieId'] + movieID=(reviews[-1])['movieId'] #movieID needed to be altered as it wasn't showing the right movie print(movieID) movie_list = db.session.query((MOVIE_DETAILS.id), @@ -361,7 +372,7 @@ def myaccount(): (MOVIE_DETAILS.release), (MOVIE_DETAILS.description), (MOVIE_DETAILS.category), - (MOVIE_DETAILS.movieLength), + (MOVIE_DETAILS.movieLength), #movie details are queried where there REVIEW_DETAILS movieId matches the movieID so it shows the right movie details per review (MOVIE_DETAILS.coverPhoto), (MOVIE_DETAILS.pagePhoto), (MOVIE_DETAILS.longDescription), @@ -376,7 +387,7 @@ def myaccount(): for user in user_list: users.append({ 'id' : user.id, 'userName' : user.userName, - 'userPassword' : user.userPassword, + 'userPassword' : user.userPassword, #user data is appended to users array 'joinDate' : user.joinDate}) movies = [] @@ -386,7 +397,7 @@ def myaccount(): 'title' : movie.title, 'release' : movie.release, 'description' : movie.description, - 'rating' : movie.averageRating, + 'rating' : movie.averageRating, #movie details are appended to movies array 'movieLength' : movie.movieLength, 'category' : movie.category, 'coverPhoto' : movie.coverPhoto}) @@ -394,4 +405,4 @@ def myaccount(): - return jsonify ({'movieDetails': movies,'userDetails': users,'userReviews': reviews}) \ No newline at end of file + return jsonify ({'movieDetails': movies,'userDetails': users,'userReviews': reviews}) #all three arrays are returned \ No newline at end of file diff --git a/flask-react/src/components/Account.js b/flask-react/src/components/Account.js index e3d284b7..3e449150 100644 --- a/flask-react/src/components/Account.js +++ b/flask-react/src/components/Account.js @@ -1,25 +1,10 @@ import React, {useEffect, useState} from 'react'; -import { Form, Button } from "semantic-ui-react"; -import { fade, makeStyles } from '@material-ui/core/styles'; -import AppBar from '@material-ui/core/AppBar'; -import Toolbar from '@material-ui/core/Toolbar'; -import IconButton from '@material-ui/core/IconButton'; -import Typography from '@material-ui/core/Typography'; -import InputBase from '@material-ui/core/InputBase'; -import Badge from '@material-ui/core/Badge'; -import MenuItem from '@material-ui/core/MenuItem'; -import { List, Header, Rating } from "semantic-ui-react"; -import Menu from '@material-ui/core/Menu'; -import MenuIcon from '@material-ui/icons/Menu'; -import SearchIcon from '@material-ui/icons/Search'; -import AccountCircle from '@material-ui/icons/AccountCircle'; -import MailIcon from '@material-ui/icons/Mail'; -import NotificationsIcon from '@material-ui/icons/Notifications'; -import MoreIcon from '@material-ui/icons/MoreVert'; -import { Reviews } from './Reviews.js'; +import { makeStyles } from '@material-ui/core/styles'; + //essential imports +import { Rating } from "semantic-ui-react"; import { useParams } from "react-router-dom"; -const useStyles = makeStyles((theme) => ({ +const useStyles = makeStyles((theme) => ({ //page styling title: { color: 'white', height: '50px', @@ -59,8 +44,9 @@ const useStyles = makeStyles((theme) => ({ joinDate: { color: 'white', textAlign: 'center', - fontSize: '10pt', - marginTop: '0px' + fontSize: '12pt', + marginBottom: '5px', + marginTop: '10px' }, reviewDetails: { color: 'white', @@ -143,43 +129,42 @@ const useStyles = makeStyles((theme) => ({ padding: '5px', fontSize: '13pt' }, - joinDate: { - fontSize: '12pt', - color: 'white', - textAlign: 'center', - marginBottom: '5px', - marginTop: '10px' - } })); export default function Account() { - const classes = useStyles(); + const classes = useStyles(); //here the classes is assigned to get access to the styling const [userDetails, setUserDetails] = useState([]); - const [userReviews, setUserReviews] = useState([]); + const [userReviews, setUserReviews] = useState([]); //using useState hook to give states to the components when rendering const [movieDetails, setMovieDetails] = useState([]); const [reviewCount, setReviewCount] = useState(0); const [myReviews] = useState([]); - let { id } = useParams(); - const userID=localStorage.getItem('userid') + let { id } = useParams(); //useParams hook gives us the right routing into the page using the user id + const userID=localStorage.getItem('userid') //localStorage provides the currently logged in users's id and authorisation token const token=localStorage.getItem('authorization') useEffect(()=> { fetch('https://arthur-laura-5000.codio-box.uk/myaccount', { method: "POST", credentials: 'include', headers: {'Content-type': 'application/json','Authorization': token}, body: JSON.stringify(id) }).then(response =>response.json().then(data => {setUserDetails(data.userDetails[0]);setMovieDetails(data.movieDetails[0]);setReviewCount(data.userReviews.length);setUserReviews(data.userReviews.pop());})); - },[]); + },[]); //fetching the data from the API URL, using the right method, credentials and authorisation token console.log(userDetails) console.log(movieDetails) console.log(userReviews) - if (userID==id){ + if (userID==id){ //client side security so that when a user is logged in the data is shown, otherwise the else path will redirect them to the login page return ( + {/*using divs the different parts of the page is built up + * classes are used to use styling for specific elements of the page + * this container shows user details therfore using userDetails.*columnName* to show the user information*/}
-  +

{userDetails.userName}

Member since: {userDetails.joinDate}

Reviews: {reviewCount}

Most recent review

+ {/*new div is started below to split up the content of the page into separate boxes + * this container shows movie and review details which is why both movideDetails and userReviews are used to display data + * the review is for the most recent review on the user's account */}
@@ -204,7 +189,7 @@ export default function Account() {
); - }else{ + }else{ //no user is logged in, redirect to login page {window.location.href='/'} } } \ No newline at end of file diff --git a/flask-react/src/components/AddReview.js b/flask-react/src/components/AddReview.js index b3034e5c..2d16fab6 100644 --- a/flask-react/src/components/AddReview.js +++ b/flask-react/src/components/AddReview.js @@ -1,26 +1,10 @@ import React, {useEffect, useState} from 'react'; -import { fade, makeStyles } from '@material-ui/core/styles'; -import AppBar from '@material-ui/core/AppBar'; -import Toolbar from '@material-ui/core/Toolbar'; -import IconButton from '@material-ui/core/IconButton'; -import Typography from '@material-ui/core/Typography'; -import InputBase from '@material-ui/core/InputBase'; -import Badge from '@material-ui/core/Badge'; -import MenuItem from '@material-ui/core/MenuItem'; -import Menu from '@material-ui/core/Menu'; -import MenuIcon from '@material-ui/icons/Menu'; -import SearchIcon from '@material-ui/icons/Search'; -import AccountCircle from '@material-ui/icons/AccountCircle'; -import MailIcon from '@material-ui/icons/Mail'; -import NotificationsIcon from '@material-ui/icons/Notifications'; -import MoreIcon from '@material-ui/icons/MoreVert'; -import NavBar from './NavBar.js'; -import SendIcon from '@material-ui/icons/Send'; -import { List, Header, Rating } from "semantic-ui-react"; -import { Reviews } from './Reviews.js'; +import { makeStyles } from '@material-ui/core/styles'; +import SendIcon from '@material-ui/icons/Send'; //essential imports +import { Rating } from "semantic-ui-react"; import { useParams } from "react-router-dom"; -const useStyles = makeStyles((theme) => ({ +const useStyles = makeStyles((theme) => ({ //page styling title: { color: 'white', fontSize: '15pt', @@ -172,42 +156,46 @@ const useStyles = makeStyles((theme) => ({ })); export default function AddReview() { - const classes = useStyles(); + const classes = useStyles(); //here the classes is assigned to get access to the styling const [movies, setMovies] = useState([]); - const [userRating,setUserRating] = useState("1"); + const [userRating,setUserRating] = useState("1"); //using useState hook to give states to the components when rendering const [userReview,setUserReview] = useState(""); var today = new Date(); var dd = String(today.getDate()).padStart(2, '0'); - var mm = String(today.getMonth() + 1).padStart(2, '0'); + var mm = String(today.getMonth() + 1).padStart(2, '0'); //generating the date of when the function is ran so that there is a date for when a review was submitted var yyyy = today.getFullYear(); today = dd + '/' + mm + '/' + yyyy; - const userID=localStorage.getItem('userid') + const userID=localStorage.getItem('userid') //local storage gives userid of current user logged in let { id } = useParams(); useEffect(()=> { fetch('https://arthur-laura-5000.codio-box.uk/addreview', { credentials: 'include', method: 'POST', headers: { 'Content-type': 'application/json'}, body: JSON.stringify(id)}).then(response =>response.json().then(data => {setMovies(data.thisMovie); }) ); - },[]); + },[]); //fetching the data from the API URL, using the right method, credentials and authorisation token console.log(movies) function handleAddReview(event) { - const details = [userRating, userReview, userID, id, today]; + const details = [userRating, userReview, userID, id, today]; //details is an array of data that is taking the user inputs, userID, movies's id and the date console.log(details) fetch('https://arthur-laura-5000.codio-box.uk/addnewreview', { method: 'POST', - credentials: 'include', + credentials: 'include', //the post to the API is then called headers: { 'Content-type': 'application/json', }, body: JSON.stringify(details), - }).then(response =>response.json().then(data => { + }).then(response =>response.json().then(data => { //the response will create some user feedback in the form of an alert and redirect them back to the movie's page which is the previous window alert("Your review has been added") window.history.back() })) event.preventDefault(); } - if (userID!==null){ + if (userID!==null){ //client side security so that when a user is logged in the data is shown, otherwise the else path will redirect them to the login page return (
+ {/*using divs the different parts of the page is built up + * classes are used to use styling for specific elements of the page + * this container shows movie details for the movie the user wishes to give a review on + * to show movie details the movies.*columnName* is used to get each piece of information to show on the page*/}
 @@ -230,7 +218,9 @@ export default function AddReview() {
- + {/*new div is started below to split up the content of the page into separate boxes + * this container is created to build the form where the user can use the rating stars to give a rating and type a review below it + * when they are finished with the review the send icon in the bottom right calls the handleAddReview function which posts the review into the database using the API */}

Add your review

Rate the movie:

@@ -242,7 +232,7 @@ export default function AddReview() {
); - }else{ + }else{ //no user is logged in, redirect to login page {window.location.href='/login'} } } \ No newline at end of file diff --git a/flask-react/src/components/AllMovies.js b/flask-react/src/components/AllMovies.js index b20d2977..ee814f3c 100644 --- a/flask-react/src/components/AllMovies.js +++ b/flask-react/src/components/AllMovies.js @@ -1,24 +1,8 @@ import React, {useEffect, useState} from 'react'; -import { fade, makeStyles } from '@material-ui/core/styles'; -import AppBar from '@material-ui/core/AppBar'; -import Toolbar from '@material-ui/core/Toolbar'; -import IconButton from '@material-ui/core/IconButton'; -import Typography from '@material-ui/core/Typography'; -import InputBase from '@material-ui/core/InputBase'; -import Badge from '@material-ui/core/Badge'; -import MenuItem from '@material-ui/core/MenuItem'; -import Menu from '@material-ui/core/Menu'; -import MenuIcon from '@material-ui/icons/Menu'; -import SearchIcon from '@material-ui/icons/Search'; -import AccountCircle from '@material-ui/icons/AccountCircle'; -import MailIcon from '@material-ui/icons/Mail'; -import NotificationsIcon from '@material-ui/icons/Notifications'; -import MoreIcon from '@material-ui/icons/MoreVert'; -import NavBar from './NavBar.js'; -import { Movies } from './Movies.js'; -import { List, Header, Rating } from "semantic-ui-react"; +import { makeStyles } from '@material-ui/core/styles'; //essential imports +import { Rating } from "semantic-ui-react"; -const useStyles = makeStyles((theme) => ({ +const useStyles = makeStyles((theme) => ({ //page styling title: { color: 'white', height: '50px', @@ -77,16 +61,20 @@ const useStyles = makeStyles((theme) => ({ })); export default function AllMovies() { - const classes = useStyles(); - const [movies, setMovies] = useState([]); + const classes = useStyles(); //here the classes is assigned to get access to the styling + const [movies, setMovies] = useState([]); //using useState hook to give states to the components when rendering useEffect(()=> { fetch('https://arthur-laura-5000.codio-box.uk/movies', { credentials: 'include' }).then(response =>response.json().then(data => {setMovies(data.movies); }) ); - },[]); + },[]); //fetching the data from the API URL console.log(movies) return (
+ {/*using divs the different parts of the page is built up + * classes are used to use styling for specific elements of the page + * this container shows all movies in the database using a loop without the need to be logged in + * to show movie details the movies.*columnName* and uses a hover state so that the data about each movie is hidden under the hover interaction */}

All movies

{movies.map(movie => { return ( diff --git a/flask-react/src/components/Categories.js b/flask-react/src/components/Categories.js index 1992d3f1..c7da44fe 100644 --- a/flask-react/src/components/Categories.js +++ b/flask-react/src/components/Categories.js @@ -1,25 +1,9 @@ import React, {useEffect, useState} from 'react'; -import { fade, makeStyles } from '@material-ui/core/styles'; -import AppBar from '@material-ui/core/AppBar'; -import Toolbar from '@material-ui/core/Toolbar'; -import IconButton from '@material-ui/core/IconButton'; -import Typography from '@material-ui/core/Typography'; -import InputBase from '@material-ui/core/InputBase'; -import Badge from '@material-ui/core/Badge'; -import MenuItem from '@material-ui/core/MenuItem'; -import Menu from '@material-ui/core/Menu'; -import MenuIcon from '@material-ui/icons/Menu'; -import SearchIcon from '@material-ui/icons/Search'; -import AccountCircle from '@material-ui/icons/AccountCircle'; -import MailIcon from '@material-ui/icons/Mail'; -import NotificationsIcon from '@material-ui/icons/Notifications'; -import MoreIcon from '@material-ui/icons/MoreVert'; -import NavBar from './NavBar.js'; -import { Movies } from './Movies.js'; -import { List, Header, Rating } from "semantic-ui-react"; +import { makeStyles } from '@material-ui/core/styles'; //essential imports +import { Rating } from "semantic-ui-react"; import { useParams } from "react-router-dom"; -const useStyles = makeStyles((theme) => ({ +const useStyles = makeStyles((theme) => ({ //page styling title: { color: 'white', height: '50px', @@ -39,18 +23,18 @@ const useStyles = makeStyles((theme) => ({ rowGap: '5px', }, cover: { - width:'220px', - height:'320px', + width:'190px', + height:'310px', position: 'relative', }, movieCard: { marginTop: '0px', marginBottom: '10px', - marginRight: '10px', - marginLeft: '10px', + marginRight: '7.5px', + marginLeft: '7.5px', float: 'left', - width: '220px', - height: '320px', + width: '190px', + height: '310px', position: 'relative', '&:hover>div': { opacity:1, @@ -78,16 +62,20 @@ const useStyles = makeStyles((theme) => ({ })); export default function Categories() { - const classes = useStyles(); - const [movies, setMovies] = useState([]); - let { category } = useParams(); + const classes = useStyles(); //here the classes is assigned to get access to the styling + const [movies, setMovies] = useState([]); //using useState hook to give states to the components when rendering + let { category } = useParams(); //useParams hook gives us the right routing into the page for the correct category useEffect(()=> { fetch('https://arthur-laura-5000.codio-box.uk/categories', { credentials: 'include', method: 'POST', headers: { 'Content-type': 'application/json'}, body: JSON.stringify(category)}).then(response =>response.json().then(data => {setMovies(data.categories);})); - },[]); + },[]); //fetching the data from the API URL for a specific category console.log(movies) console.log(category) return (
+ {/* using divs the different parts of the page is built up + * classes are used to use styling for specific elements of the page + * this container shows all movies in the database for the specific category using a loop without the need to be logged in + * to show movie details the movie.*columnName* is used and a hover state so that the data about each movie is hidden under the hover interaction */}

{category}

{movies.map(movie => { return ( diff --git a/flask-react/src/components/HomePage.js b/flask-react/src/components/HomePage.js index 404179b6..d752c0e1 100644 --- a/flask-react/src/components/HomePage.js +++ b/flask-react/src/components/HomePage.js @@ -1,23 +1,8 @@ import React, {useEffect, useState} from 'react'; -import { fade, makeStyles } from '@material-ui/core/styles'; -import AppBar from '@material-ui/core/AppBar'; -import Toolbar from '@material-ui/core/Toolbar'; -import IconButton from '@material-ui/core/IconButton'; -import Typography from '@material-ui/core/Typography'; -import InputBase from '@material-ui/core/InputBase'; -import Badge from '@material-ui/core/Badge'; -import MenuItem from '@material-ui/core/MenuItem'; -import Menu from '@material-ui/core/Menu'; -import MenuIcon from '@material-ui/icons/Menu'; -import SearchIcon from '@material-ui/icons/Search'; -import AccountCircle from '@material-ui/icons/AccountCircle'; -import MailIcon from '@material-ui/icons/Mail'; -import NotificationsIcon from '@material-ui/icons/Notifications'; -import MoreIcon from '@material-ui/icons/MoreVert'; -import NavBar from './NavBar.js'; +import { makeStyles } from '@material-ui/core/styles'; //essential imports import { Movies } from './Movies.js'; -const useStyles = makeStyles((theme) => ({ +const useStyles = makeStyles((theme) => ({ //page styling title: { color: 'white', height: '50px', @@ -26,19 +11,23 @@ const useStyles = makeStyles((theme) => ({ marginTop: '0px', marginBottom: '0px', marginLeft: '15px', - letterSpacing: '3px' + letterSpacing: '3px', }, })); export default function HomePage() { - const classes = useStyles(); - const [movies, setMovies] = useState([]); + const classes = useStyles(); //here the classes is assigned to get access to the styling + const [movies, setMovies] = useState([]); //using useState hook to give states to the components when rendering useEffect(()=> { fetch('https://arthur-laura-5000.codio-box.uk/movies', { credentials: 'include' }).then(response =>response.json().then(data => {setMovies(data.movies);})); - },[]); + },[]); //fetching the data from the API URL console.log(movies) return ( + {/* using React.Fragment this time to group a list of children without adding extra nodes to the DOM + * this function calles for the Movies function in the AllMovies.js file which means that the styling is copied over + * this page shows the top 10 movies in the database without the need to be logged in + * to show the top 10 movies the data is sliced and sorted by rating, and will only show the first 10 out of the all movies */}

Top 10 movies

(b.rating > a.rating) ? 1 : -1).slice(0, 10)}/>
diff --git a/flask-react/src/components/Login.js b/flask-react/src/components/Login.js index 19833695..b50a26e2 100644 --- a/flask-react/src/components/Login.js +++ b/flask-react/src/components/Login.js @@ -1,22 +1,7 @@ import React, { useState } from "react"; -import { Form, Button } from "semantic-ui-react"; -import { fade, makeStyles } from '@material-ui/core/styles'; -import AppBar from '@material-ui/core/AppBar'; -import Toolbar from '@material-ui/core/Toolbar'; -import IconButton from '@material-ui/core/IconButton'; -import Typography from '@material-ui/core/Typography'; -import InputBase from '@material-ui/core/InputBase'; -import Badge from '@material-ui/core/Badge'; -import MenuItem from '@material-ui/core/MenuItem'; -import Menu from '@material-ui/core/Menu'; -import MenuIcon from '@material-ui/icons/Menu'; -import SearchIcon from '@material-ui/icons/Search'; -import AccountCircle from '@material-ui/icons/AccountCircle'; -import MailIcon from '@material-ui/icons/Mail'; -import NotificationsIcon from '@material-ui/icons/Notifications'; -import MoreIcon from '@material-ui/icons/MoreVert'; +import { makeStyles } from '@material-ui/core/styles'; //essential imports -const useStyles = makeStyles((theme) => ({ +const useStyles = makeStyles((theme) => ({ //page styling container: { marginTop: '14%', backgroundColor: '#16161A', @@ -83,36 +68,40 @@ const useStyles = makeStyles((theme) => ({ })); export default function Login() { - const classes = useStyles(); - const [username, setUsername] = useState(""); + const classes = useStyles(); //here the classes is assigned to get access to the styling + const [username, setUsername] = useState(""); //using useState hook to give states to the components when rendering const [password, setPassword] = useState(""); - const [errorMessage, setErrorMessage] = useState(""); function handleSubmit(event) { - const token = `Basic ` +btoa(`${username}:${password}`) + const token = `Basic ` +btoa(`${username}:${password}`) //the authorisation token is set up console.log(token) fetch('https://arthur-laura-5000.codio-box.uk/login', { method: 'GET', - credentials: 'include', + credentials: 'include', //the login API has a GET request to get the user input confirmed headers: { 'Content-type': 'application/json', 'Authorization': token }, }).then(response =>response.json().then(data => { if(data.userID==null){ - alert("Incorrect credentials!") + alert("Incorrect credentials!") //if the API returns null the user details that were input are incorrect }else{ localStorage.setItem('userid',data.userID) - localStorage.setItem('authorization',token) + localStorage.setItem('authorization',token) //if the login details are right, the localStorage items are set to the input data so the user is logged in localStorage.setItem('username', username) window.location.href='/' - alert("Your have logged in successfully") + alert("Your have logged in successfully") //the user is then alerted to let them know they are logged in and redirected to the home page }})) event.preventDefault(); } return (
+ {/* using divs the different parts of the page is built up + * classes are used to use styling for specific elements of the page + * this container shows is used to create the login form for the user to fill in when logging in + * the input tags allow the user to input their details, the password type hides the text by replacing it with dots + * the login button calles the function handleSubmit to initiate the login API */}
 diff --git a/flask-react/src/components/MovieForm.js b/flask-react/src/components/MovieForm.js deleted file mode 100644 index ddccd258..00000000 --- a/flask-react/src/components/MovieForm.js +++ /dev/null @@ -1,49 +0,0 @@ -import React, {useState} from 'react'; -import { Form, Input, Rating, Button } from "semantic-ui-react"; - -export const MovieForm = ({onNewMovie}) => { - const [title, setTitle] = useState(''); - const [rating, setRating] = useState(1); - - return ( -
- - setTitle(e.target.value)}/> - - - - {setRating(data.rating); - }} - /> - - - - - - -
- ) -} diff --git a/flask-react/src/components/MoviePage.js b/flask-react/src/components/MoviePage.js index 8325ce03..8dae6a7d 100644 --- a/flask-react/src/components/MoviePage.js +++ b/flask-react/src/components/MoviePage.js @@ -1,23 +1,7 @@ import React, {useEffect, useState} from 'react'; -import { fade, makeStyles } from '@material-ui/core/styles'; -import AppBar from '@material-ui/core/AppBar'; -import Toolbar from '@material-ui/core/Toolbar'; -import IconButton from '@material-ui/core/IconButton'; -import Typography from '@material-ui/core/Typography'; -import InputBase from '@material-ui/core/InputBase'; -import Badge from '@material-ui/core/Badge'; -import MenuItem from '@material-ui/core/MenuItem'; -import Menu from '@material-ui/core/Menu'; -import MenuIcon from '@material-ui/icons/Menu'; -import SearchIcon from '@material-ui/icons/Search'; -import AccountCircle from '@material-ui/icons/AccountCircle'; -import MailIcon from '@material-ui/icons/Mail'; -import NotificationsIcon from '@material-ui/icons/Notifications'; -import MoreIcon from '@material-ui/icons/MoreVert'; -import NavBar from './NavBar.js'; -import { Movies } from './Movies.js'; +import { makeStyles } from '@material-ui/core/styles'; import { useParams } from "react-router-dom"; -import { List, Header, Rating } from "semantic-ui-react"; +import { Rating } from "semantic-ui-react"; import { AllReviews } from "./MoviePageReviews.js"; const useStyles = makeStyles((theme) => ({ @@ -183,19 +167,23 @@ const useStyles = makeStyles((theme) => ({ })); export default function MoviePage() { - const classes = useStyles(); + const classes = useStyles(); //here the classes is assigned to get access to the styling const [movies, setMovies] = useState([]); - const [reviews, setReviews] = useState([]); - let { id } = useParams(); + const [reviews, setReviews] = useState([]); //using useState hook to give states to the components when rendering + let { id } = useParams(); //useParams hook gives us the right routing into the correct movie page useEffect(()=> { - fetch('https://arthur-laura-5000.codio-box.uk/movieReviews', { method: "POST", credentials: 'include', headers: {'Content-type': 'application/json'}, body:JSON.stringify(id) }).then(response =>response.json().then(data => {setMovies(data.movieDetails[0]);setReviews(data.movieReviews);})); - },[]); + fetch('https://arthur-laura-5000.codio-box.uk/movieReviews', { method: "POST", credentials: 'include', headers: {'Content-type': 'application/json'}, body:JSON.stringify(id) }).then(response =>response.json().then(data => {setMovies(data.movieDetails[0]);setReviews(data.movieReviews.reverse());})); + },[]); //fetching the data from the API URL for the specific movie id console.log(movies) console.log(id) console.log(reviews) return (
+ {/* using divs the different parts of the page is built up + * classes are used to use styling for specific elements of the page + * this container shows the specific movie's cover pagePhoto with details about the movie with the average rating below and a link to the add review page + * to show movie details the movies.*columnName* is used */}

Average rating

@@ -210,11 +198,13 @@ export default function MoviePage() {
{(() => { + {/* if a movie has no reviews yet it will show a message */} if(reviews.length==0){ return(

No reviews yet

) }else{ + {/* otherwise it will import the reviews of the movie using the AllReviews function from MoviePageReviews.js */} return (

Movie reviews

diff --git a/flask-react/src/components/MoviePageReviews.js b/flask-react/src/components/MoviePageReviews.js index 42948bb9..0feb8e8e 100644 --- a/flask-react/src/components/MoviePageReviews.js +++ b/flask-react/src/components/MoviePageReviews.js @@ -1,9 +1,7 @@ -import React, {useEffect,useState} from 'react'; -import NavBar from './NavBar.js'; -import { List, Header, Rating } from "semantic-ui-react"; -import { fade, makeStyles } from '@material-ui/core/styles'; +import { Rating } from "semantic-ui-react"; //essential imports +import { makeStyles } from '@material-ui/core/styles'; -const useStyles = makeStyles((theme) => ({ +const useStyles = makeStyles((theme) => ({ //page styling title: { color: 'white', fontSize: '25pt', @@ -42,22 +40,6 @@ const useStyles = makeStyles((theme) => ({ float: 'left', marginTop: '20px' }, - profileCover: { - width: '100px', - height: '100px', - position: 'relative', - backgroundColor: '#16161A', - marginBottom: '8px', - marginTop: '80px', - marginLeft: '30px', - float: 'left' - }, - container2: { - width: '98%', - backgroundColor: '#16161A', - rowGap: '5px', - margin: '15px', - }, reviewCard: { backgroundColor: '#0B0C0D', height: '275px', @@ -112,10 +94,14 @@ const useStyles = makeStyles((theme) => ({ } })); -export const AllReviews = ({ myReviews }) => { - const classes = useStyles(); +export const AllReviews = ({ myReviews }) => { //myReviews is a list of reviews to show on the page + const classes = useStyles(); //here the classes is assigned to get access to the styling return (
+ {/* using divs the different parts of the page is built up + * classes are used to use styling for specific elements of the page + * this container shows all reviews under the movie details on the movie page + * to show reviews the review.*columnName* is used */} {myReviews.map(review => { return (
diff --git a/flask-react/src/components/Movies.js b/flask-react/src/components/Movies.js index a57ffa8e..8af6717b 100644 --- a/flask-react/src/components/Movies.js +++ b/flask-react/src/components/Movies.js @@ -1,9 +1,7 @@ -import React, {useEffect,useState} from 'react'; -import NavBar from './NavBar.js'; -import { List, Header, Rating } from "semantic-ui-react"; -import { fade, makeStyles } from '@material-ui/core/styles'; +import { Rating } from "semantic-ui-react"; //essential imports +import { makeStyles } from '@material-ui/core/styles'; -const useStyles = makeStyles((theme) => ({ +const useStyles = makeStyles((theme) => ({ //page styling title: { color: 'white', fontSize: '12pt', @@ -54,10 +52,14 @@ const useStyles = makeStyles((theme) => ({ } })); -export const Movies = ({ movies }) => { - const classes = useStyles(); +export const Movies = ({ movies }) => { //movies is a list of movies to show on the page + const classes = useStyles(); //here the classes is assigned to get access to the styling return (
+ {/* using divs the different parts of the page is built up + * classes are used to use styling for specific elements of the page + * this container shows the movies details and can be used by page to import the styling and data such as the HomePage.js + * to show the data the movie.*columnName* is used through a loop of each movie */} {movies.map(movie => { return (
{window.location.href='/movies/'+movie.id}}> diff --git a/flask-react/src/components/MoviesRecommended.js b/flask-react/src/components/MoviesRecommended.js index ee317ddc..af8d1602 100644 --- a/flask-react/src/components/MoviesRecommended.js +++ b/flask-react/src/components/MoviesRecommended.js @@ -1,7 +1,5 @@ -import React, {useEffect,useState} from 'react'; -import NavBar from './NavBar.js'; -import { List, Header, Rating } from "semantic-ui-react"; -import { fade, makeStyles } from '@material-ui/core/styles'; +import { Rating } from "semantic-ui-react"; +import { makeStyles } from '@material-ui/core/styles'; const useStyles = makeStyles((theme) => ({ title: { @@ -53,10 +51,14 @@ const useStyles = makeStyles((theme) => ({ } })); -export const MoviesRecommended = ({ movies }) => { - const classes = useStyles(); +export const MoviesRecommended = ({ movies }) => { //movies is a list of movies to show on the page + const classes = useStyles(); //here the classes is assigned to get access to the styling return (
+ {/* using divs the different parts of the page is built up + * classes are used to use styling for specific elements of the page + * this container shows the movie details of the reccommended movies to the logged in user + * to show the data the movie.*columnName* is used through a loop of each movie*/} {movies.map(movie => { return (
{window.location.href='/movies/'+movie.id}}> diff --git a/flask-react/src/components/MyReviews.js b/flask-react/src/components/MyReviews.js index 1d111b91..aca72cf7 100644 --- a/flask-react/src/components/MyReviews.js +++ b/flask-react/src/components/MyReviews.js @@ -1,23 +1,8 @@ import React, {useEffect, useState} from 'react'; -import { fade, makeStyles } from '@material-ui/core/styles'; -import AppBar from '@material-ui/core/AppBar'; -import Toolbar from '@material-ui/core/Toolbar'; -import IconButton from '@material-ui/core/IconButton'; -import Typography from '@material-ui/core/Typography'; -import InputBase from '@material-ui/core/InputBase'; -import Badge from '@material-ui/core/Badge'; -import MenuItem from '@material-ui/core/MenuItem'; -import Menu from '@material-ui/core/Menu'; -import MenuIcon from '@material-ui/icons/Menu'; -import SearchIcon from '@material-ui/icons/Search'; -import AccountCircle from '@material-ui/icons/AccountCircle'; -import MailIcon from '@material-ui/icons/Mail'; -import NotificationsIcon from '@material-ui/icons/Notifications'; -import MoreIcon from '@material-ui/icons/MoreVert'; -import NavBar from './NavBar.js'; +import { makeStyles } from '@material-ui/core/styles'; //essential imports import { Reviews } from './Reviews.js'; -const useStyles = makeStyles((theme) => ({ +const useStyles = makeStyles((theme) => ({ //page styling title: { color: 'white', fontSize: '25pt', @@ -29,23 +14,26 @@ const useStyles = makeStyles((theme) => ({ })); export default function MyReviews() { - const classes = useStyles(); - const [myReviews, setReviews] = useState([]); - const userID=localStorage.getItem('userid') + const classes = useStyles(); //here the classes is assigned to get access to the styling + const [myReviews, setReviews] = useState([]); //using useState hook to give states to the components when rendering + const userID=localStorage.getItem('userid') //local storage gives userid and token of current user logged in const token=localStorage.getItem('authorization') useEffect(()=> { fetch('https://arthur-laura-5000.codio-box.uk/myReviews', { credentials: 'include', headers: {'Content-type': 'application/json','Authorization': token} }).then(response =>response.json().then(data => {setReviews(data.myReviews);})); - },[]); + },[]); //fetching the data from the API URL to get the reviews of the currently logged in user console.log(myReviews) console.log(myReviews.length) - if (userID!==null){ + if (userID!==null){ //client side security so that when a user is logged in the data is shown, otherwise the else path will redirect them to the login page return ( -

My Reviews

+ {/* using React.Fragment this time to group a list of children without adding extra nodes to the DOM + * this function calles for the Reviews function in the Reviews.js file which means that the styling is copied over + * this page shows the currently logged in user's reviews */} +

My Reviews - Highest to Lowest rated

); - }else{ + }else{ //no user is logged in, redirect to login page {window.location.href='/login'} } } \ No newline at end of file diff --git a/flask-react/src/components/NavBar.js b/flask-react/src/components/NavBar.js index 416ca627..a0821d26 100644 --- a/flask-react/src/components/NavBar.js +++ b/flask-react/src/components/NavBar.js @@ -1,5 +1,4 @@ - -import React, {useEffect, useState} from 'react'; +import React from 'react'; import { fade, makeStyles } from '@material-ui/core/styles'; import AppBar from '@material-ui/core/AppBar'; import Toolbar from '@material-ui/core/Toolbar'; @@ -7,7 +6,7 @@ import IconButton from '@material-ui/core/IconButton'; import Typography from '@material-ui/core/Typography'; import InputBase from '@material-ui/core/InputBase'; import Badge from '@material-ui/core/Badge'; -import MenuItem from '@material-ui/core/MenuItem'; +import MenuItem from '@material-ui/core/MenuItem'; //essential imports import Menu from '@material-ui/core/Menu'; import MenuIcon from '@material-ui/icons/Menu'; import SearchIcon from '@material-ui/icons/Search'; @@ -16,9 +15,8 @@ import MailIcon from '@material-ui/icons/Mail'; import NotificationsIcon from '@material-ui/icons/Notifications'; import MoreIcon from '@material-ui/icons/MoreVert'; import CloseIcon from '@material-ui/icons/Close'; -import { Movies } from './Movies.js'; -const useStyles = makeStyles((theme) => ({ +const useStyles = makeStyles((theme) => ({ //page styling grow: { flexGrow: 1, }, @@ -190,21 +188,19 @@ const useStyles = makeStyles((theme) => ({ title: { '&:hover': { textDecoration: 'underline', - transition:'backgroundColor color .25s ease-in-out', }, } })); export default function NavBar() { - const classes = useStyles(); + const classes = useStyles(); //here the classes is assigned to get access to the styling const [account, setAccount] = React.useState(false); - const [sideDrawer, setSideDrawer] = React.useState(false); + const [sideDrawer, setSideDrawer] = React.useState(false); //using useState hook to give states to the components when rendering const [mobileMoreAnchorEl, setMobileMoreAnchorEl] = React.useState(null); const isMobileMenuOpen = Boolean(mobileMoreAnchorEl); - - const handleMobileMenuClose = () => { + const handleMobileMenuClose = () => { //functions to handle mobile version of the navbar setMobileMoreAnchorEl(null); }; @@ -217,21 +213,21 @@ export default function NavBar() { }; const menuId = 'primary-search-account-menu'; - const userID=localStorage.getItem('userid') + const userID=localStorage.getItem('userid') //get localStorage when user is logged in const username=localStorage.getItem('username') - const handleLogout = () => { + const handleLogout = () => { //handle logout when the logout button is clicked localStorage.removeItem('userid') - localStorage.removeItem('authorization') + localStorage.removeItem('authorization') //removes localStorage to remove access to secure pages localStorage.removeItem('username') window.location.href='/' - alert("Your have logged out successfully") + alert("Your have logged out successfully") //give alert to user that they are logged out }; const mobileMenuId = 'primary-search-account-menu-mobile'; const renderMobileMenu = ( + {/* using divs the different parts of the page is built up + * classes are used to use styling for specific elements of the page + * this div is used to display all the text and icons in in the navbar and use them to call functions such as onclick and redirect */} {setSideDrawer(sideDrawer => !sideDrawer)}} @@ -281,7 +280,7 @@ export default function NavBar() { aria-label="open drawer" disableRipple= "true" > - {window.location.href='/allmovies'}} variant="h6" style = {{fontSize: 15,textAlign:'left', marginLeft:'30px', width:'49px', position:'relative', cursor:'pointer'}} noWrap> + {window.location.href='/allmovies'}} variant="h6" style = {{fontSize: 15,textAlign:'left', marginLeft:'30px', width:'50px', position:'relative', cursor:'pointer'}} noWrap> Movies { if (event.key === 'Enter'){ - window.location=('/search/'+event.target.value)}}} + window.location=('/search/'+event.target.value)}}} //the search bar is craeted to call the search API and display the results depending on the input />
- {setAccount(account => !account)}} + {setAccount(account => !account)}} //onclick display account side drawer edge="end" aria-label="account of current user" aria-controls={menuId} @@ -358,7 +357,7 @@ export default function NavBar() { {renderMobileMenu} {(() => { - if(account){ + if(account){ //if account side darawer is open styling is called by the classes return(
@@ -367,7 +366,7 @@ export default function NavBar() {
{(() => { - if(userID==null){ + if(userID==null){ // if there is no user logged in, the links are to the login or register page or a close icon to get rid of the side drawer return(

Not logged in @@ -377,7 +376,7 @@ export default function NavBar() {

{window.location.href='/register'}}>Register

- )}else{ + )}else{ // if there is a user logged in, the links are to the account page or log out which calls the handleLogout function or a close icon to get rid of the side drawer return(

{username} @@ -393,7 +392,7 @@ export default function NavBar() { )} })()} {(() => { - if(sideDrawer){ + if(sideDrawer){ //if the hamburger icon is click whether logged in or not, it reveals a side drawer on the left with links to the different category movies and a close icon to get rid off the side drawer return(

diff --git a/flask-react/src/components/Recommended.js b/flask-react/src/components/Recommended.js index 58cfc771..501ff5a5 100644 --- a/flask-react/src/components/Recommended.js +++ b/flask-react/src/components/Recommended.js @@ -1,21 +1,5 @@ import React, {useEffect, useState} from 'react'; -import { fade, makeStyles } from '@material-ui/core/styles'; -import AppBar from '@material-ui/core/AppBar'; -import Toolbar from '@material-ui/core/Toolbar'; -import IconButton from '@material-ui/core/IconButton'; -import Typography from '@material-ui/core/Typography'; -import InputBase from '@material-ui/core/InputBase'; -import Badge from '@material-ui/core/Badge'; -import MenuItem from '@material-ui/core/MenuItem'; -import Menu from '@material-ui/core/Menu'; -import MenuIcon from '@material-ui/icons/Menu'; -import { List, Header, Rating } from "semantic-ui-react"; -import SearchIcon from '@material-ui/icons/Search'; -import AccountCircle from '@material-ui/icons/AccountCircle'; -import MailIcon from '@material-ui/icons/Mail'; -import NotificationsIcon from '@material-ui/icons/Notifications'; -import MoreIcon from '@material-ui/icons/MoreVert'; -import NavBar from './NavBar.js'; +import { makeStyles } from '@material-ui/core/styles'; import { MoviesRecommended } from './MoviesRecommended.js'; const useStyles = makeStyles((theme) => ({ @@ -78,19 +62,23 @@ const useStyles = makeStyles((theme) => ({ })); export default function Recommended() { - const classes = useStyles(); - const [moviesRecommended, setMoviesRecommended] = useState([]); - const [copy, setCopy] = useState([]); - const userID=localStorage.getItem('userid') + const classes = useStyles(); //here the classes is assigned to get access to the styling + const [moviesRecommended, setMoviesRecommended] = useState([]); //using useState hook to give states to the components when rendering + const userID=localStorage.getItem('userid') //local storage gives userid and token of current user logged in const token=localStorage.getItem('authorization') useEffect(()=> { fetch('https://arthur-laura-5000.codio-box.uk/recommended', { method: "POST", credentials: 'include', headers: {'Content-type': 'application/json','Authorization': token}, body: JSON.stringify(userID) }).then(response =>response.json().then(data => {setMoviesRecommended(data.moviesRecommended); - }) + }) //fetching the data from the API URL to get the recommended movies of the currently logged in user ); },[]); console.log(moviesRecommended) + if (userID!==null){ //client side security so that when a user is logged in the data is shown, otherwise the else path will redirect them to the login page return (
+ {/*using divs the different parts of the page is built up + * classes are used to use styling for specific elements of the page + * this container shows a list of movies for each category that the user has reviewed only when the user is logged in + * to show recommended movies this function calls for the function MoviesRecommend from MoviesRecommended.js to get the data and styling */} {moviesRecommended.map(movie => { return (
@@ -101,4 +89,7 @@ export default function Recommended() { })}
); + }else{ //no user is logged in, redirect to login page + {window.location.href='/login'} + } } \ No newline at end of file diff --git a/flask-react/src/components/Register.js b/flask-react/src/components/Register.js index 060863f4..564bae0f 100644 --- a/flask-react/src/components/Register.js +++ b/flask-react/src/components/Register.js @@ -1,22 +1,7 @@ import React, { useState } from "react"; -import { Form, Button } from "semantic-ui-react"; -import { fade, makeStyles } from '@material-ui/core/styles'; -import AppBar from '@material-ui/core/AppBar'; -import Toolbar from '@material-ui/core/Toolbar'; -import IconButton from '@material-ui/core/IconButton'; -import Typography from '@material-ui/core/Typography'; -import InputBase from '@material-ui/core/InputBase'; -import Badge from '@material-ui/core/Badge'; -import MenuItem from '@material-ui/core/MenuItem'; -import Menu from '@material-ui/core/Menu'; -import MenuIcon from '@material-ui/icons/Menu'; -import SearchIcon from '@material-ui/icons/Search'; -import AccountCircle from '@material-ui/icons/AccountCircle'; -import MailIcon from '@material-ui/icons/Mail'; -import NotificationsIcon from '@material-ui/icons/Notifications'; -import MoreIcon from '@material-ui/icons/MoreVert'; +import { makeStyles } from '@material-ui/core/styles'; //essential imports -const useStyles = makeStyles((theme) => ({ +const useStyles = makeStyles((theme) => ({ //page styling container: { marginTop: '12%', backgroundColor: '#16161A', @@ -83,42 +68,46 @@ const useStyles = makeStyles((theme) => ({ })); export default function Register() { - const classes = useStyles(); - const [username, setUsername] = useState(""); - const [password, setPassword] = useState(""); - const [passwordConfirm, setpasswordConfirm] = useState(""); - const [errorMessage, setErrorMessage] = useState(""); + const classes = useStyles(); //here the classes is assigned to get access to the styling + const [username, setUsername] = useState(""); //using useState hook to give states to the components when rendering + const [password, setPassword] = useState(""); + const [passwordConfirm, setpasswordConfirm] = useState(""); var today = new Date(); var dd = String(today.getDate()).padStart(2, '0'); - var mm = String(today.getMonth() + 1).padStart(2, '0'); //January is 0! + var mm = String(today.getMonth() + 1).padStart(2, '0'); //generating the date of when the function is ran so that there is a date for when a user account is created var yyyy = today.getFullYear(); today = dd + '/' + mm + '/' + yyyy; function handleSubmit(event) { - const details = [username, password, today]; + const details = [username, password, today]; //details is an array of data that is taking the user inputs, username, password and the date fetch('https://arthur-laura-5000.codio-box.uk/register', { method: 'POST', - credentials: 'include', + credentials: 'include', //the post to the API is then called headers: { 'Content-type': 'application/json', }, body: JSON.stringify(details), }).then(response =>response.json().then(data => { - if(password!=passwordConfirm){ - alert("Passwords don't match!") + if(password!=passwordConfirm){ //a client side check is ran to check whethere the password and passwordConfirm match + alert("Passwords don't match!") //if they don't, an alert is displayed for the user }else{ - localStorage.setItem('userid',data.userID) - const token = `Basic ` +btoa(`${username}:${password}`) + localStorage.setItem('userid',data.userID) //if the passwords match and the username isn't yet taken, the userID is set in the localStorage + const token = `Basic ` +btoa(`${username}:${password}`) //the token is assigned localStorage.setItem('authorization', token) - localStorage.setItem('username', username) + localStorage.setItem('username', username) //the token and username are set in the localStorage window.location.href='/' - alert("Your have successfully registered") + alert("Your have successfully registered") //the user is redirected to the home page and recives an alert to say they have been registered }})) event.preventDefault(); } return (
+ {/* using divs the different parts of the page is built up + * classes are used to use styling for specific elements of the page + * this container shows is used to create the register form for the user to fill in when registering + * the input tags allow the user to input their details, the password type hides the text by replacing it with dots + * the register button calles the function handleSubmit to initiate the register API */}
 setUsername(e.target.value)}/> diff --git a/flask-react/src/components/Reviews.js b/flask-react/src/components/Reviews.js index e518d505..aed8554b 100644 --- a/flask-react/src/components/Reviews.js +++ b/flask-react/src/components/Reviews.js @@ -1,11 +1,9 @@ -import React, {useEffect,useState} from 'react'; -import NavBar from './NavBar.js'; -import { List, Header, Rating } from "semantic-ui-react"; -import { fade, makeStyles } from '@material-ui/core/styles'; +import React from 'react'; +import { Rating } from "semantic-ui-react"; +import { makeStyles } from '@material-ui/core/styles'; //essential imports import DeleteForeverIcon from '@material-ui/icons/DeleteForever'; -import CloseIcon from '@material-ui/icons/Close'; -const useStyles = makeStyles((theme) => ({ +const useStyles = makeStyles((theme) => ({ //page styling title: { color: 'white', fontSize: '25pt', @@ -48,6 +46,7 @@ const useStyles = makeStyles((theme) => ({ marginBottom: '8px', marginLeft: '15px', marginTop: '8px', + cursor: 'pointer' }, reviewDateCSS: { marginTop: '80px', @@ -87,12 +86,12 @@ const useStyles = makeStyles((theme) => ({ }, confirmBox: { width: '450px', - height: '220px', + height: '160px', backgroundColor: '#0B0C0D', position: 'absolute', left: '500px', zIndex: '1', - top: '280px', + top: '300px', }, topConfirmBox: { backgroundColor: '#16161A', @@ -110,7 +109,7 @@ const useStyles = makeStyles((theme) => ({ }, option1: { color: 'white', - marginTop: '20px', + marginTop: '0px', marginLeft: '75px', fontSize: '15pt', textAlign: 'center', @@ -128,7 +127,7 @@ const useStyles = makeStyles((theme) => ({ }, option2: { color: 'white', - marginTop: '60px', + marginTop: '20px', marginLeft: '50px', fontSize: '15pt', textAlign: 'center', @@ -154,27 +153,27 @@ const useStyles = makeStyles((theme) => ({ })); export const Reviews = ({ myReviews }) => { - const classes = useStyles(); + const classes = useStyles(); //here the classes is assigned to get access to the styling const userID=localStorage.getItem('userid') - const token=localStorage.getItem('authorization') + const token=localStorage.getItem('authorization') //local storage gives userid and token of current user logged in console.log(myReviews) - const [removeConfirm, setRemoveConfirm] = React.useState(false); - const [movieID, setMovieID] = React.useState(''); - async function removeReview(movieId){ - const details={userID,movieId} + const [removeConfirm, setRemoveConfirm] = React.useState(false); //using useState hook to give states to the components when rendering + const [movieID, setMovieID] = React.useState(''); + async function removeReview(movieId){ + const details={userID,movieId} //details are taken from the page so that the API knows which userID is removed which review by the movieID console.log(details) const response = await fetch('https://arthur-laura-5000.codio-box.uk/removereview', { method: 'POST', - credentials: 'include', + credentials: 'include', //the post to the API is then called headers: { 'Content-type': 'application/json', 'Authorization': token }, body: JSON.stringify(details), }) - if(response.ok){ + if(response.ok){ //if the response of the API is 201 an alert will be display to feedback to the user alert("Your review has been removed") - window.location.reload() + window.location.reload() //the page is then reloaded to show the review is removed from the user's my reviews page } } return ( @@ -182,7 +181,11 @@ export const Reviews = ({ myReviews }) => { {(() => { if(removeConfirm){ return( - + = + {/*using divs the different parts of the page is built up + * classes are used to use styling for specific elements of the page + * this container shows movie details for the movie the user wishes to give a review on + * to show movie details the movies.*columnName* is used to get each piece of information to show on the page*/}
@@ -200,7 +203,7 @@ export const Reviews = ({ myReviews }) => { return (
-  + {window.location.href='/movies/'+review.movieId}} alt=" " />

Your rating

diff --git a/flask-react/src/components/SearchResults.js b/flask-react/src/components/SearchResults.js index 2a9972b6..e73702b4 100644 --- a/flask-react/src/components/SearchResults.js +++ b/flask-react/src/components/SearchResults.js @@ -78,7 +78,7 @@ const useStyles = makeStyles((theme) => ({ })); export default function SearchResults() { - const classes = useStyles(); + const classes = useStyles(); //here the classes is assigned to get access to the styling const [movies, setMovies] = useState([]); let { searchTerm } = useParams(); useEffect(()=> {