all repos — moviedb @ 5e82b1b6e63b51d448914dbe5872b5d255b1b594

A Flask web-app to browse movie information from IMDb using imdbpy

Initial commit
Prithu Goswami prithugoswami524@gmail.com
Wed, 15 Apr 2020 18:00:59 +0530
commit

5e82b1b6e63b51d448914dbe5872b5d255b1b594

A .gitignore

@@ -0,0 +1,4 @@

+*.pyc +__pycache__/ + +tmp_test/
A Procfile

@@ -0,0 +1,1 @@

+web: gunicorn "moviedb:create_app()"
A README.md

@@ -0,0 +1,13 @@

+# moviedb + +A Flask web-app that uses imdbpy to browser movies. A user can also add movies +to his favorites list. + +Try it out: https://project-moviedb.herokuapp.com/ + +![home](https://user-images.githubusercontent.com/28246690/69314049-2c1de200-0c59-11ea-8a2f-d65044786176.png) +![info](https://user-images.githubusercontent.com/28246690/69314086-3dff8500-0c59-11ea-8d08-64d1c70b0d1e.png) +![info_fav](https://user-images.githubusercontent.com/28246690/69314092-40fa7580-0c59-11ea-8133-72d18936e912.png) +![login](https://user-images.githubusercontent.com/28246690/69314102-448dfc80-0c59-11ea-9b94-3a1d1ecc5a1e.png) +![profile](https://user-images.githubusercontent.com/28246690/69314107-4788ed00-0c59-11ea-9093-597221c2cb6a.png) +![search_results](https://user-images.githubusercontent.com/28246690/69314110-49eb4700-0c59-11ea-90c2-2a5207d26f4d.png)
A TODO.md

@@ -0,0 +1,11 @@

+- TITLE +- YEAR +- RATING +- RUNTIME +- GENRES +- DIRECTORS +- PLOT* +- POSTER* + +*- will require internet access through api. So will not work without internet +but still have their section.
A moviedb/__init__.py

@@ -0,0 +1,29 @@

+from flask import Flask, session, g +from flask_cors import CORS +from flask_sqlalchemy import SQLAlchemy +import os + +db = SQLAlchemy() + +def create_app(): + app = Flask(__name__) + + # Set the db_url variable or else it will read the env variable + # or else environment variable DATABASE_URL will be used + db_url="" + if os.environ['DATABASE_URL']: + db_url = os.environ['DATABASE_URL'] + + app.config['SQLALCHEMY_DATABASE_URI'] = db_url + app.config['SECRET_KEY'] = 'thisisasecret' + + db.init_app(app) + CORS(app) + + from .auth import auth as auth_blueprint + app.register_blueprint(auth_blueprint) + + from .main import main as main_blueprint + app.register_blueprint(main_blueprint) + + return app
A moviedb/auth.py

@@ -0,0 +1,65 @@

+from flask import (Blueprint, render_template, redirect, + url_for, request, flash) +from werkzeug.security import generate_password_hash, check_password_hash +from .models import user_info, fav +from . import db, session, g + +auth = Blueprint('auth', __name__) + +@auth.route('/login') +def login(): + newuser = request.args.get('nu', None) + return render_template("login.html", newuser=newuser).replace( + '<html lang="en"', + '<html lang="en" style="background-image:url(../static/img/bg.jpg)"' + ,1) + +@auth.route('/login', methods=['POST']) +def login_post(): + username = request.form.get('username') + password = request.form.get('password') + remember = True if request.form.get('remember') else False + + user = user_info.query.filter_by(username=username).first() + + if not user or not check_password_hash(user.password, password): + flash('Please check your login details and try again.') + return redirect(url_for('auth.login')) + else: + session['user'] = username + g.user = session['user'] + return redirect(url_for('main.profile')) + +@auth.route('/signup') +def signup(): + return render_template("signup.html").replace('<html lang="en"', + '<html lang="en" style="background-image:url(../static/img/bg.jpg)"' + ,1) + +@auth.route('/signup', methods=['POST']) +def signup_post(): + username = request.form.get('username') + password = request.form.get('password') + + if not username or not password: + flash("Username or Password cannot be emtpy") + return redirect(url_for('auth.signup')) + + user = user_info.query.filter_by(username=username).first() + + if user: + flash('Username already exists') + return redirect(url_for('auth.signup')) + + new_user = user_info(username=username, password=generate_password_hash( + password, method='sha256')) + + db.session.add(new_user) + db.session.commit() + + return redirect(url_for('auth.login', nu=1)) + +@auth.route('/logout') +def logout(): + session['user'] = None + return redirect(url_for('auth.login'))
A moviedb/main.py

@@ -0,0 +1,257 @@

+from flask import (Flask, render_template, request, url_for, Blueprint, g, + Response) +from . import db, session, g +from .models import user_info, fav +from imdb import IMDb +from pprint import pprint as pp +import tmdbsimple as tmdb +import json + + + +tmdb.API_KEY = 'b888b64c9155c26ade5659ea4dd60e64' + +main = Blueprint('main', __name__) + + +# local - use local mysql db +local=False + +# enable_extra - loads poster and plot overview from tmdb for movie info +enable_extra=True + +# to laod posters on profile page +posters_on_profile_page=False + +tmdb_img_url = r'https://image.tmdb.org/t/p/w342' + + +if local: + ia = IMDb('s3', 'mysql+mysqldb://may:venom123@localhost/imdb') +else: + ia = IMDb() + +def db_fav_exists(tconst, user_id): + """ + checks if the tconst exists as a favorite for the user of user id `user_id` + """ + fav_tconst = fav.query.filter_by(user_id=user_id).all() + if fav_tconst: # user already has favorites + for a in fav_tconst: + if a.tconst == int(tconst): # the same tconst already exists + return True + + return False + +def db_get_userid(username): + """ + get the user id of the username from the user_info table + """ + user = user_info.query.filter_by(username=username).first() + if user: + return user.user_id + else: + return -1 + + +@main.route('/') +def index(): + # I know this is a very hacky way of doing this; I am already cringing + return render_template("index.html").replace('<html lang="en"', + '<html lang="en" style="background-image:url(../static/img/bg.jpg)"' + ,1) + +@main.route('/search') +def search(): + query = request.args.get('q', None) + if query: + q_res = ia.search_movie(query) + results = [] + if local: + for m in q_res: + try: + results.append({ + 'id': m.getID(), + 'title': m['title'], + 'year': m['startYear'], + 'kind': m['kind'] + }) + except KeyError: + pass + else: #if not local + results = [{ + 'id': m.getID(), + 'cover': m['cover url'], + 'title': m['title'], + 'year': m.get('year', ''), + 'kind': m['kind'] + } for m in q_res] + + return render_template("search.html", results=results, + local=local).replace('<html lang="en"', + '<html lang="en" style="background-color:#efefef"', + 1) + else: #not query + return ('') + +@main.route('/profile') +def profile(): + if not session.get('user', None): + return render_template('profile.html') + + user_id = db_get_userid(session['user']) + fav_query = fav.query.filter_by(user_id=user_id).all() + fav_tconsts = [a.tconst for a in fav_query] + user = user_info.query.filter_by(user_id=user_id).first() + + user_data = {} + user_data['movies'] = [] + user_data['favorite_count'] = len(fav_tconsts) + user_data['favorite_genre'] = user.fav_genre + user_data['recent_fav'] = user.recent_fav + for a in fav_tconsts: + movie = {} + mov = ia.get_movie(a) + + long_title = mov.get('long imdb title') + genres = (", ".join(mov.get('genres', []))).title() + rating = mov.get('rating', None) + cover = None + + if posters_on_profile_page: + find = tmdb.Find('tt{:07}'.format(int(a))) + find.info(external_source='imdb_id') + cover_path = None + + if find.movie_results: + cover_path = find.movie_results[0].get('poster_path', None) + elif find.tv_results: + cover_path = find.tv_results[0].get('poster_path', None) + + if cover_path: + cover = tmdb_img_url + cover_path + + movie = { + 'id': a, + 'long title': long_title, + 'rating': rating if rating else '', + 'genres': genres, + 'cover': cover if cover else '' + } + + user_data['movies'].append(movie) + + return render_template('profile.html', + user_data=user_data, + posters_on_profile_page=posters_on_profile_page) + +@main.route('/info') +def info(): + movid = request.args.get('id', None) + if not movid: + return ('') + else: + mov = ia.get_movie(movid) + movie={} + + #collect all the relevent info in a dict 'movie' + long_title = mov.get('long imdb title') + title = mov.get('title') + rating = mov.get('rating', None) + genres = (", ".join(mov.get('genres', []))).title() + runmin = 0 + if mov.get('runtime'): + runmin = int(mov.get('runtime', ['0'])[0]) + runtime = "{}h {}m".format(runmin//60, runmin%60) + + director = '' + writer = '' + if mov.get('director'): + director = mov.get('director')[0]['name'] + if mov.get('writer'): + writer = mov.get('writer')[0]['name'] + + cover = mov.get('full-size cover url', None) + plot = mov.get('plot', [''])[0].split('::')[0] + + if enable_extra: + find = tmdb.Find('tt{:07}'.format(int(movid))) + find.info(external_source='imdb_id') + if (find.movie_results and find.movie_results[0]['poster_path'] + and find.movie_results[0]['overview']): + cover = tmdb_img_url + find.movie_results[0]['poster_path'] + plot = find.movie_results[0]['overview'] + elif (find.tv_results and find.tv_results[0]['poster_path'] + and find.tv_results[0]['overview']): + cover = tmdb_img_url + find.tv_results[0]['poster_path'] + plot = find.tv_results[0]['overview'] + + movie = { + 'id': mov.getID(), + 'long title': long_title, + 'title': title, + 'rating': rating if rating else '', + 'genres': genres, + 'runtime': runtime, + 'director': director, + 'writer': writer, + 'plot': plot if plot else '', + 'cover': cover if cover else '' + } + + isfavorite = False + if session.get('user', None): + isfavorite = db_fav_exists(tconst = movie['id'], + user_id = db_get_userid(session['user'])) + + + return render_template("info.html", + movie=movie, + isfavorite=isfavorite).replace( + '<html lang="en"', + '<html lang="en" style="background-color:#efefef"', + 1) + +@main.route('/fav', methods=['POST']) +def favorite(): + resp = {} + if not session.get('user', None): + resp['status'] = 'error' + resp['message'] = 'not logged in' + return Response(json.dumps(resp), + status=401, + mimetype='application/json') + + remove = request.form.get('remove', None) + tconst = request.form.get('tconst', None) + + user_id = db_get_userid(session['user']) + # user = user_info.query.filter_by(username=session['user']).first() + # user_id = user.user_id + + if db_fav_exists(tconst=tconst, user_id=user_id): + if remove: + toremove = fav.query.filter_by(user_id=user_id, + tconst=int(tconst)).first() + + db.session.delete(toremove) + db.session.commit() + resp['status'] = 'success' + resp['actiontype'] = 'remove' + resp['message'] = 'removed the title from favorites' + else: + resp['status'] = 'warning' + resp['actiontype'] = 'none' + resp['message'] = 'title already in favorites' + return Response(json.dumps(resp), + status=200, + mimetype='application/json') + + db.session.add(fav(tconst=tconst, user_id=user_id)) + db.session.commit() + resp['status'] = 'success' + resp['actiontype'] = 'add' + resp['message'] = 'added to favorites' + return Response(json.dumps(resp), + status=200, + mimetype='application/json')
A moviedb/models.py

@@ -0,0 +1,12 @@

+from . import db + +class user_info(db.Model): + user_id = db.Column(db.Integer, primary_key=True) + password = db.Column(db.String(100)) + username = db.Column(db.String(100), unique=True) + fav_genre = db.Column(db.String(100)) + recent_fav = db.Column(db.String) + +class fav(db.Model): + user_id = db.Column(db.Integer, primary_key=True) + tconst = db.Column(db.Integer, primary_key=True)
A moviedb/static/css/custom.css

@@ -0,0 +1,623 @@

+*, *::before, *::after { + box-sizing: inherit; +} + +h1, h2, h3, h4, h5, h6 { + font-size: 100%; + font-weight: 400; +} + +blockquote, body, dd, dl, dt, fieldset, figure, h1, h2, h3, h4, h5, h6, hr, html, iframe, legend, li, ol, p, pre, textarea, ul { + margin: 0; + padding: 0; +} + +html { + box-sizing: border-box; +} + +html { + background-color: white; + font-size: 16px; + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + min-width: 300px; + overflow-x: hidden; + overflow-y: scroll; + text-rendering: optimizeLegibility; + -webkit-text-size-adjust: 100%; + -moz-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + text-size-adjust: 100%; +} + +p { + margin: 0; +} + +a { + text-decoration: none; + color: inherit; +} + +body { + color: #4a4a4a; + font-size: 1em; + font-weight: 400; + line-height: 1.5; + font-family: Roboto; +} + +.button, .input { + -moz-appearance: none; + -webkit-appearance: none; + align-items: center; + border: 1px solid transparent; + border-radius: 4px; + box-shadow: none; + display: inline-flex; + font-size: 1rem; + height: 2.5em; + justify-content: flex-start; + line-height: 1.5; + position: relative; + vertical-align: top; +} + +.button:focus { + outline: none; +} + +nav { + background-color: rgba(0, 0, 0, 0.9); + height: 64px; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + box-shadow: 0 5px 20px rgba(0,0,0,0.1); +} + +nav a{ + font-family: "Roboto"; + text-decoration: none; + text-transform: uppercase; + font-weight: 700; + color: white; +} + +.nav-left { + margin-left: 2rem; + width: 50%; + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-start; +} + +.nav-right { + margin-right: 2rem; + width: 50%; + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-end; +} + +.nav-left .nav-item:not(:first-child) { + margin-left: 2rem; +} + +.nav-right .nav-item:not(:last-child) { + margin-right: 2rem; +} + +.nav-left .nav-item { + margin: 0; +} + +nav a:hover{ + color: yellow; +} + +.nav-username { + color: rgba(255, 255, 255, 0.8); + font-size: 90%; + font-family: "Roboto Mono"; + text-transform: none; +} + +.landing{ + background: url(../img/bg.jpg); + display: flex; + flex-direction: column; + height: 70%; +} + +.search_body{ + margin-top: 20vh; + height: 100%; +} + +.mycontainer{ + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.search{ + display: flex; + background: #FFF; + width: 90%; + max-width: 650px; + height: 50px; + border-radius: 2.8rem; + align-items: center; + justify-content: space-between; + box-shadow: 0 10px 20px 3px rgba(0,0,0,0.15); +} + +.srch-input{ + height: 50px; + width: 90%; + border-radius: 2.8rem; + border: none; + box-sizing: border-box; + font-family: Roboto; + font-style: style; + font-size: 20px; + padding-left: 2rem; +} + +.material-icons.md-36.grey { + margin-right: 1.5rem; + font-size: 30px; + color: rgba(0,0,0, 0.7); +} + +.disapear { + display: none; +} + +.fav-bg { + background: rgb(255,195,195); + background: linear-gradient(215deg, rgba(255,195,195,1) 0%, rgba(255,237,237,1) 20%, rgba(255,250,250,1) 100%); +} + +.movieinfo { + background-color: white; + padding: 2.5rem; + border-radius: 1.6rem; + box-shadow: 0 10px 20px 3px rgba(0,0,0,0.15); + margin-top: 2.5rem; + width: 80%; + display: flex; + justify-content: center; + align-items: flex-start; +} + +.poster{ + flex-shrink: 0; + width: 30%; +} + +.infotext { + padding-left: 2.6rem; +} + +.infotext-head { + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 2rem; +} +.infotext-head .material-icons.md-fav { + font-size: 2rem; +} + +.movie-title { + font-size: 2rem; + font-family: Roboto; + line-height: 100%; + padding-right: 2rem; +} + +@media only screen and (min-width: 1400px){ + .movie-title {font-size: 2.5rem} +} + +.rating { + display: flex; + flex-direction: row; + justify-content: left; + align-items: center; +} + +.material-icons.gold { + font-size: 2rem; + color: rgb(255,163,20); + padding-right: 5px; +} + +.material-icons.md-fav { + font-size: 1.2rem; + color: rgb(247, 101, 106); +} +.fav .material-icons.md-fav { + margin-left: 0.7rem; +} + + +@media only screen and (min-width: 1400px){ + .material-icons.gold {font-size: 2.5rem} +} + +.rating-no { + font-size: 1.4rem; + font-family: "Roboto Mono"; +} + +@media only screen and (min-width: 1400px){ + .rating-no {font-size: 1.75rem} +} + +.fav-text { + font-family: Roboto; + color: rgb(247, 101, 106); + margin-left: 0.3rem; + margin-right: 0.7rem; + margin-bottom: 0.1rem; +} + +.fav{ + margin-top: 1rem; + background-color: rgba(255, 255, 255, 0); + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + min-width: 8rem; + height: 2.6rem; + border-radius: 0.4rem; + padding: 0px; + border: 2px solid rgba(247, 101, 106, 1); +} + +.fav:focus { + outline: none; +} + +.fav:active { + box-shadow: 0px 5px 7px rgba(0,0,0,0.1); +} + +.fav:hover .fav-text { + color: white; +} + +.fav:hover .material-icons.md-fav { + color: white; +} + +.fav:hover { + background-color: rgba(247, 101, 106, 1); + border: 2px solid rgba(247, 101, 106, 0); +} + + +.genres { + font-family: "Roboto Mono"; + font-size: 1rem; +} + +@media only screen and (min-width: 1400px){ + .genres {font-size: 1.25rem} +} + +.runtime{ + padding: 0.2rem 0.8rem; + margin: 0.4rem 0rem; + max-width: 5rem; + font-size: 1rem; + font-family: Roboto; + border-radius: 8px; + display: inline-block; + background-color: rgba(0,0,0,0.13) +} + +@media only screen and (min-width: 1400px){ + .runtime { + margin: 0.6rem 0rem; + padding: 0.3rem 0.9rem; + font-size: 1.25rem; + max-width: 6rem; + + } +} + +.plot{ + margin-top: 1rem; + font-family: Roboto; + font-size: 1.2rem; + line-height: 1.4rem; + margin-bottom: 1.2rem; + border-left: 0.3rem solid grey; + padding-left: 0.6rem; +} + +@media only screen and (min-width: 1400px){ + .plot{ + font-size: 1.5rem; + line-height: 1.75rem; + } +} + +.director{ + font-size: 0.9rem; + font-family: Roboto; +} + + +.writer{ + font-size: 0.9rem; + font-family: Roboto; +} + +@media only screen and (min-width: 1400px){ + .director{ + font-size: 1.2rem; + } + .writer{ + font-size: 1.2rem; + } +} + +.results{ + margin-top: 2rem; + width: 70%; + display: flex; + flex-direction: column; +} + +.result-box{ + background-color: white; + padding: 1rem 1.5rem; + margin-bottom: 0.8rem; + border-radius: 1rem; + box-shadow: 0 7px 10px 3px rgba(0,0,0,0.1); + display: flex; +} + +.kind { + font-family: "Roboto Mono", mono; + font-size: 0.9rem; +} + +.res-text{ + margin-left: 1rem; +} + +.res-title{ + font-family: "Roboto"; + font-weight: 500; +} + +.res-poster{ + display: inline-block; + min-width: 60px; + width: 5%; +} + +/* .signup{ */ +/* margin-top: 4rem; */ +/* display: flex; */ +/* flex-direction: column; */ +/* background: pink; */ +/* width: 40%; */ +/* } */ + +/* .singup form input{ */ +/* display: block; */ +/* } */ + + +.profile { + background-color: white; + padding: 2.5rem; + border-radius: 1.6rem; + box-shadow: 0 10px 20px 3px rgba(0,0,0,0.15); + margin-top: 2.5rem; + width: 80%; + display: flex; + flex-direction: column; + margin-bottom: 4rem; +} + +.profile-head h1{ + font-weight: 700; + font-size: 2.5rem; +} + +.profile-username { + font-family: "Roboto Mono"; + font-size: 1.2rem; +} + +.divider { + padding-top: 2rem; + width: 100%; + border-bottom: 1px solid rgba(0, 0, 0, 0.5); +} + +.profile-head{ + display: flex; + align-items: center; + justify-content: space-between; +} + +.profile-head-left{ + width: 50%; + display: flex; + flex-direction: column; +} + +.profile-head-right{ + width: 50%; + display: flex; + flex-direction: column; + align-items: flex-end; +} + +.profile-fav-count{ + display: flex; + align-items: center; +} + +.profile-fav-count i{ + padding-right: 0.4rem; +} + +.profile-fav-genre { + font-weight: 500; +} + +.profile h2 { + margin-top: 1rem; + font-size: 1.4rem; + font-weight: 500; +} + +.profile-list-item { + display: flex; + align-items: flex-start; +} + +.profile-list-item { + padding: 0.8rem 0.8rem; + display: flex; + align-items: flex-start; + border-radius: 0.8rem; +} + +.profile-list-item:hover { + background-color: #eaeaea; +} + +.profile-list-item:focus { + outline: none; +} + +.profile-list-item:active { + box-shadow: 0 5px 10px rgba(0,0,0,0); + outline: none; +} + +.profile-list-item-text { + padding-left: 1rem; +} +.profile-list-title { + font-size: 1.4rem; +} + +.profile-list-rating { + display: flex; + justify-content: left; + align-items: center; +} + +.profile-list-rating .material-icons.gold { + font-size: 1.4rem; +} + +.profile-list-rating .rating-no { + font-size: 0.9rem; + font-family: "Roboto Mono"; +} + +.profile-list-poster{ + display: inline-block; + min-width: 60px; + width: 10%; +} + +.box { + background-color: white; + border-radius: 6px; + box-shadow: 0 0.5em 1em -0.125em rgba(10, 10, 10, 0.1), 0 0px 0 1px rgba(10, 10, 10, 0.02); + color: #4a4a4a; + display: block; + padding: 1.25rem; +} + +.notification { + background-color: whitesmoke; + border-radius: 4px; + padding: 1.25rem 2.5rem 1.25rem 1.5rem; + position: relative; +} + +.notification.is-info { + background-color: #3298dc; + color: #fff; +} + +.notification.is-danger { + background-color: #f14668; + color: #fff; + margin-bottom: 1rem; +} + +.title { + font-weight: 700; + font-size: 2rem; + font-family: Roboto; +} + +.has-text-light { + color: whitesmoke !important; +} + +.field:not(:last-child) { + margin-bottom: 0.75rem; +} + +.control { + box-sizing: border-box; + clear: both; + font-size: 1rem; + text-align: left; +} + +.input { + background-color: white; + border-radius: 4px; + color: #363636; + box-shadow: inset 0 1px 2px rgba(10, 10, 10, 0.2); + padding: 1rem; + box-sizing: border-box; + max-width: 100%; + width: 100%; +} + +.input:focus { + box-shadow: 0 0 0 0.125em rgba(50, 115, 220, 0.25); +} + +.button { + justify-content: center; + padding: 0.5em 1em; + white-space: nowrap; + background-color: #3298dc; + color: #fff; + display: flex; + width: 100%; +} + +.authbox { + display: flex; + flex-direction: column; + width: 33%; + min-width: 300px; +}
A moviedb/static/css/normalize.css

@@ -0,0 +1,427 @@

+/*! normalize.css v3.0.2 | MIT License | git.io/normalize */ + +/** + * 1. Set default font family to sans-serif. + * 2. Prevent iOS text size adjust after orientation change, without disabling + * user zoom. + */ + +html { + font-family: sans-serif; /* 1 */ + -ms-text-size-adjust: 100%; /* 2 */ + -webkit-text-size-adjust: 100%; /* 2 */ +} + +/** + * Remove default margin. + */ + +body { + margin: 0; +} + +/* HTML5 display definitions + ========================================================================== */ + +/** + * Correct `block` display not defined for any HTML5 element in IE 8/9. + * Correct `block` display not defined for `details` or `summary` in IE 10/11 + * and Firefox. + * Correct `block` display not defined for `main` in IE 11. + */ + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; +} + +/** + * 1. Correct `inline-block` display not defined in IE 8/9. + * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. + */ + +audio, +canvas, +progress, +video { + display: inline-block; /* 1 */ + vertical-align: baseline; /* 2 */ +} + +/** + * Prevent modern browsers from displaying `audio` without controls. + * Remove excess height in iOS 5 devices. + */ + +audio:not([controls]) { + display: none; + height: 0; +} + +/** + * Address `[hidden]` styling not present in IE 8/9/10. + * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. + */ + +[hidden], +template { + display: none; +} + +/* Links + ========================================================================== */ + +/** + * Remove the gray background color from active links in IE 10. + */ + +a { + background-color: transparent; +} + +/** + * Improve readability when focused and also mouse hovered in all browsers. + */ + +a:active, +a:hover { + outline: 0; +} + +/* Text-level semantics + ========================================================================== */ + +/** + * Address styling not present in IE 8/9/10/11, Safari, and Chrome. + */ + +abbr[title] { + border-bottom: 1px dotted; +} + +/** + * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. + */ + +b, +strong { + font-weight: bold; +} + +/** + * Address styling not present in Safari and Chrome. + */ + +dfn { + font-style: italic; +} + +/** + * Address variable `h1` font-size and margin within `section` and `article` + * contexts in Firefox 4+, Safari, and Chrome. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +/** + * Address styling not present in IE 8/9. + */ + +mark { + background: #ff0; + color: #000; +} + +/** + * Address inconsistent and variable font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` affecting `line-height` in all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +/* Embedded content + ========================================================================== */ + +/** + * Remove border when inside `a` element in IE 8/9/10. + */ + +img { + border: 0; +} + +/** + * Correct overflow not hidden in IE 9/10/11. + */ + +svg:not(:root) { + overflow: hidden; +} + +/* Grouping content + ========================================================================== */ + +/** + * Address margin not present in IE 8/9 and Safari. + */ + +figure { + margin: 1em 40px; +} + +/** + * Address differences between Firefox and other browsers. + */ + +hr { + -moz-box-sizing: content-box; + box-sizing: content-box; + height: 0; +} + +/** + * Contain overflow in all browsers. + */ + +pre { + overflow: auto; +} + +/** + * Address odd `em`-unit font size rendering in all browsers. + */ + +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} + +/* Forms + ========================================================================== */ + +/** + * Known limitation: by default, Chrome and Safari on OS X allow very limited + * styling of `select`, unless a `border` property is set. + */ + +/** + * 1. Correct color not being inherited. + * Known issue: affects color of disabled elements. + * 2. Correct font properties not being inherited. + * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. + */ + +button, +input, +optgroup, +select, +textarea { + color: inherit; /* 1 */ + font: inherit; /* 2 */ + margin: 0; /* 3 */ +} + +/** + * Address `overflow` set to `hidden` in IE 8/9/10/11. + */ + +button { + overflow: visible; +} + +/** + * Address inconsistent `text-transform` inheritance for `button` and `select`. + * All other form control elements do not inherit `text-transform` values. + * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. + * Correct `select` style inheritance in Firefox. + */ + +button, +select { + text-transform: none; +} + +/** + * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` + * and `video` controls. + * 2. Correct inability to style clickable `input` types in iOS. + * 3. Improve usability and consistency of cursor style between image-type + * `input` and others. + */ + +button, +html input[type="button"], /* 1 */ +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; /* 2 */ + cursor: pointer; /* 3 */ +} + +/** + * Re-set default cursor for disabled elements. + */ + +button[disabled], +html input[disabled] { + cursor: default; +} + +/** + * Remove inner padding and border in Firefox 4+. + */ + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} + +/** + * Address Firefox 4+ setting `line-height` on `input` using `!important` in + * the UA stylesheet. + */ + +input { + line-height: normal; +} + +/** + * It's recommended that you don't attempt to style these elements. + * Firefox's implementation doesn't respect box-sizing, padding, or width. + * + * 1. Address box sizing set to `content-box` in IE 8/9/10. + * 2. Remove excess padding in IE 8/9/10. + */ + +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Fix the cursor style for Chrome's increment/decrement buttons. For certain + * `font-size` values of the `input`, it causes the cursor style of the + * decrement button to change from `default` to `text`. + */ + +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +/** + * 1. Address `appearance` set to `searchfield` in Safari and Chrome. + * 2. Address `box-sizing` set to `border-box` in Safari and Chrome + * (include `-moz` to future-proof). + */ + +input[type="search"] { + -webkit-appearance: textfield; /* 1 */ + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; /* 2 */ + box-sizing: content-box; +} + +/** + * Remove inner padding and search cancel button in Safari and Chrome on OS X. + * Safari (but not Chrome) clips the cancel button when the search input has + * padding (and `textfield` appearance). + */ + +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * Define consistent border, margin, and padding. + */ + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +/** + * 1. Correct `color` not being inherited in IE 8/9/10/11. + * 2. Remove padding so people aren't caught out if they zero out fieldsets. + */ + +legend { + border: 0; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Remove default vertical scrollbar in IE 8/9/10/11. + */ + +textarea { + overflow: auto; +} + +/** + * Don't inherit the `font-weight` (applied by a rule above). + * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. + */ + +optgroup { + font-weight: bold; +} + +/* Tables + ========================================================================== */ + +/** + * Remove most spacing between table cells. + */ + +table { + border-collapse: collapse; + border-spacing: 0; +} + +td, +th { + padding: 0; +}
A moviedb/static/css/style.css

@@ -0,0 +1,89 @@

+body{ + background-image: url("../img/background2.jpg"); +} + +/*.header{ + margin-top: -30px; + margin-left:-10px; + margin-right:-10px; + font-family: Arial, Helvetica, sans-serif; + font-size: 20px; + color: rgb(76, 88, 4); + text-align:left; + background-color: rgb(2, 26, 18); + height:100px; + background-image: url("../img/header2.jpg") +} +h1{ +} +*/ +.form1{ + align-content: center; +} +.form2{ + align-content: center; +} +.form3{ + align-content: center; +} + +h1{ + margin-left:680px; +} + +.search_block{ + margin-left:720px; + margin-top:200px; +} + +.input{ + border:20px; + border-color:red; + margin-left:-200px; + height:50px; + width:900px; +} + +#b1{ + position: absolute; + margin-top:0px; + margin-left:20px; + height:50px; + width:90px; +} + +#b2{ + height: 200px; + width : 200px; + margin-left:-90px; + background-image: url("../img/5Star.jpg"); +} + +#b3{ + position: absolute; + height: 200px; + width : 200px; + margin-left: 400px; + margin-top:-200px; + background-image: url("../img/trending.jpg") +} + +#b3:hover{ + background-color: rgba(9, 109, 6, 0.842); + transform:scale(1.2,1.2); + -webkit-transform:scale(1.2,1.2); + -moz-transform:scale(1.2,1.2); + +} +#b2:hover{ + background-color: rgba(9, 109, 6, 0.842); + transform:scale(1.2,1.2); + -webkit-transform:scale(1.2,1.2); + -moz-transform:scale(1.2,1.2); +} +#b1:hover{ + background-color: darkcyan; + transform:scale(1.1,1.2); + -webkit-transform:scale(1.2,1.2); + -moz-transform:scale(1.2,1.2); +}
A moviedb/templates/base.html

@@ -0,0 +1,44 @@

+<!DOCTYPE html> +<html lang="en"> +<head> + + <meta charset="utf-8"> + <title>{% block title %}{% endblock %} - moviedb</title> + <meta name="description" content=""> + <meta name="author" content=""> + + <meta name="viewport" content="width=device-width, initial-scale=1"> + + <link href="//fonts.googleapis.com/css?family=Roboto:400,500,900" rel="stylesheet" type="text/css"> + <link href="//fonts.googleapis.com/css?family=Roboto+Mono:500" rel="stylesheet" type="text/css"> + <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> + + <link rel="stylesheet" href="{{ url_for('static', filename="css/normalize.css")}}"> + <link rel="stylesheet" href="{{ url_for('static', filename="css/custom.css")}}"> + + +</head> +<body> + <nav> + <div class="nav-left"> + <p class="nav-item"><a href="{{ url_for('main.index') }}">Home</a></li></p> + </div> + <div class="nav-right"> + {% if not session['user'] %} + <p class="nav-item"><a href="{{ url_for('auth.login') }}">Login</a></p> + <p class="nav-item"><a href="{{ url_for('auth.signup') }}">Sign up</a></p> + {% else %} + <p class="nav-username nav-item"> + Logged in as <span style="color:white;font-weight:700">{{ session['user'] }}</span></p> + <p class="nav-item"><a href="{{ url_for('auth.logout') }}">Logout</a></p> + <p class="nav-item"><a href="{{ url_for('main.profile') }}">Profile</a></p> + {% endif %} + </div> + </nav> +{% block content %} +{% endblock %} + +{% block script %} +{% endblock script %} +</body> +</html>
A moviedb/templates/index.html

@@ -0,0 +1,15 @@

+{% extends 'base.html' %} + +{% block title %}Search For Movies and TV Shows{% endblock %} + +{% block content %} +<div class="search_body"> + <div class="mycontainer"> + <form class="search" action="{{ url_for('main.search') }}" method="get"> + <input name="q" type=text class="srch-input" placeholder="Search for Movies/TV Shows"> + <i class="material-icons md-36 grey" > search </i> + </input> + </form> + </div> +</div> +{% endblock %}
A moviedb/templates/info.html

@@ -0,0 +1,95 @@

+{% extends "base.html" %} + +{% block title %}{{ movie['long title'] }}{% endblock %} + +{% block content %} +<div class="mycontainer"> + <div id="movieinfo" class="movieinfo {% if isfavorite %}fav-bg{% endif %}"> + {% if movie['cover'] %} + <img class="poster" src="{{ movie['cover'] }}"> + {% endif %} + <div class="infotext"> + <div class="infotext-head"> + <h1 class="movie-title">{{ movie['long title']}}</h1> + <i id="heart" class="{% if isfavorite %}material-icons md-fav + {% else %}disapear{% endif %}">favorite</i> + </div> + {% if movie['rating'] %} + <div class="rating"> + <i class="material-icons gold"> star </i> + <p class="rating-no">{{ movie['rating'] }}</p> + </div> + {% endif %} + <p class="genres">{{ movie['genres'] }} </p> + <p class="runtime">{{ movie['runtime'] }}</p> + <p class="plot"> {{ movie['plot'] }} </p> + <p class="director">Director: {{ movie['director'] }}</p> + <p class="writer">Writer: {{ movie['writer'] }}</p> + {% if isfavorite %} + <button id="favButton" type=submit class="fav" name="remove" value="{{ movie['id'] }}"> + <i class="material-icons md-fav">cancel</i> + <p class="fav-text">remove from favorites</p> + </button> + {% elif not isfavorite and session.get('user', None) %} + <button id="favButton" type=submit class="fav" name="add" value="{{ movie['id'] }}"> + <i class="material-icons md-fav">favorite</i> + <p class="fav-text">add to favorites</p> + </button> + {% endif %} + </div> + </div> +</div> +{% endblock %} + +{% block script %} + +<script> +(function() { + var httpReq = new XMLHttpRequest(); + var formdata = new FormData(); + + var favButton = document.getElementById("favButton"); + var tconst = favButton.getAttribute("value"); + var bg = document.getElementById("movieinfo"); + var heart = document.getElementById("heart"); + + + favButton.addEventListener('click', handleReq); + + function handleReq() { + var action = favButton.getAttribute("name"); + httpReq.onreadystatechange = notify; + // httpReq.open('POST', 'http://localhost:5000/fav'); + httpReq.open('POST', 'https://project-moviedb.herokuapp.com/fav'); + formdata.append('tconst', tconst); + if (action == "remove"){ + formdata.append('remove', 1); + } + httpReq.send(formdata); + } + + function notify() { + if (httpReq.readyState === XMLHttpRequest.DONE) { + var response = JSON.parse(httpReq.responseText); + if (response.status == "success"){ + if (response.actiontype == "add"){ + favButton.firstElementChild.innerHTML = "cancel"; + favButton.lastElementChild.innerHTML = "remove from favorites"; + favButton.setAttribute("name", "remove"); + bg.className="movieinfo fav-bg"; + heart.className="material-icons md-fav" + } + else if (response.actiontype == "remove"){ + favButton.firstElementChild.innerHTML = "favorite"; + favButton.lastElementChild.innerHTML = "add to favorites"; + favButton.setAttribute("name", "add"); + bg.className="movieinfo"; + heart.className="disapear " + } + } + } + } +})(); +</script> + +{% endblock %}
A moviedb/templates/login.html

@@ -0,0 +1,39 @@

+{% extends "base.html" %} + +{% block title %}Login{% endblock %} + +{% block content %} +<div class="mycontainer" style="margin-top:4rem"> + <div class="authbox"> + <h3 class="title has-text-light">Login</h3> + <div class="box"> + {% with messages = get_flashed_messages() %} + {% if messages %} + <div class="notification is-danger"> + {{ messages[0] }} + </div> + {% endif %} + {% endwith %} + {% if newuser %} + <div class="notification is-info"> + User Successfully Registered. You can now Log in + </div> + {% endif %} + <form method="POST" action="/login"> + <div class="field"> + <div class="control"> + <input class="input" type="text" name="username" placeholder="Your username" autofocus=""> + </div> + </div> + + <div class="field"> + <div class="control"> + <input class="input" type="password" name="password" placeholder="Your password"> + </div> + </div> + <button class="button is-block is-info is-fullwidth">Login</button> + </form> + </div> + </div> +</div> +{% endblock %}
A moviedb/templates/profile.html

@@ -0,0 +1,55 @@

+{% extends "base.html" %} + +{% block title %}{{ session['user'] }}'s Profile{% endblock %} + +{% block content %} + +{% if session['user'] %} +<div class="mycontainer"> + <div class="profile"> + <div class="profile-head"> + <div class="profile-head-left"> + <h1>{{ session['user'] }}</h1> + <p class="profile-fav-count"> + <i class="material-icons md-fav">favorite</i> + {{ user_data['favorite_count'] }} Favorites + </p> + </div> + <div class="profile-head-right"> + <p class="profile-fav-genre"> + Favorite Genre: + <strong>{{ user_data['favorite_genre'] }}</strong> + </p> + <p class="profile-fav-genre"> + Recent Favorite: + <strong>{{ user_data['recent_fav'] }}</strong> + </p> + </div> + </div> + <div class="divider"></div> + <h2>Favorite Titles</h2> + {% for movie in user_data['movies'] %} + <a href="{{ url_for('main.info', id=movie['id']) }}" class="profile-list-item"> + {% if posters_on_profile_page %} + <img class="profile-list-poster" src="{{ movie['cover'] }}"> + {% endif %} + <div class="profile-list-item-text"> + <p class="profile-list-title">{{ movie['long title'] }}</p> + <p class="genres">{{ movie['genres'] }}</p> + <div class="profile-list-rating"> + <i class="material-icons gold"> star </i> + <p class="rating-no">{{ movie['rating'] }}</p> + </div> + </div> + </a> + {% endfor %} + </div> +</div> +{% else %} +<h1> You need to + <a class="has-text-primary" href="{{ url_for('auth.login') }}">Login</a> + in order to view your profile +</h1> +{% endif %} + +{% endblock %}
A moviedb/templates/search.html

@@ -0,0 +1,21 @@

+{% extends "base.html" %} + +{% block title %}Search Results{% endblock %} + +{% block content %} +<div class="mycontainer"> + <div class=results> + {% for res in results %} + <a class=result-box href="{{ url_for('main.info', id=res['id']) }}"> + {%if res['cover'] %} + <img src="{{ res['cover'] }}" class=res-poster> + {% endif %} + <div class="res-text"> + <h3 class=res-title>{{res['title']}} ({{res['year']}})</h3> + <p class="kind">type: {{res['kind']}}</p> + </div> + </a> + {% endfor %} + </div> +</div> +{% endblock %}
A moviedb/templates/signup.html

@@ -0,0 +1,36 @@

+{% extends "base.html" %} + +{% block title %}SignUp{% endblock %} + +{% block content %} +<div class="mycontainer" style="margin-top:4rem"> + <div class="authbox"> + <h3 class="title has-text-light">Sign Up</h3> + <div class="box"> + {% with messages = get_flashed_messages() %} + {% if messages %} + <div class="notification is-danger"> + {{ messages[0] }} + </div> + {% endif %} + {% endwith %} + <form method="POST" action="/signup"> + <div class="field"> + <div class="control"> + <input class="input" type="text" required name="username" placeholder="Name" autofocus=""> + </div> + </div> + + <div class="field"> + <div class="control"> + <input class="input" type="password" required name="password" placeholder="Password"> + </div> + </div> + + <button class="button is-block is-info is-fullwidth">Sign Up</button> + </form> + </div> + </div> +</div> +{% endblock %} +
A mysql/dumps/README.md

@@ -0,0 +1,6 @@

+### Getting the imdb data + +The data was collected from [imdb.com/interfaces](https://imdb.com/interfaces) +and then migrated into a MariaDb database using [imdbpy's script][1] + +[1]: https://imdbpy.readthedocs.io/en/latest/usage/s3.html
A mysql/dumps/fav.sql

@@ -0,0 +1,120 @@

+-- MariaDB dump 10.17 Distrib 10.4.10-MariaDB, for Linux (x86_64) +-- +-- Host: localhost Database: imdb +-- ------------------------------------------------------ +-- Server version 10.4.10-MariaDB + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `fav` +-- + +DROP TABLE IF EXISTS `fav`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `fav` ( + `user_id` int(11) NOT NULL, + `tconst` int(11) NOT NULL, + PRIMARY KEY (`user_id`,`tconst`), + KEY `tconst` (`tconst`), + CONSTRAINT `fav_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `user_info` (`user_id`), + CONSTRAINT `fav_ibfk_2` FOREIGN KEY (`tconst`) REFERENCES `title_basics` (`tconst`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `fav` +-- + +LOCK TABLES `fav` WRITE; +/*!40000 ALTER TABLE `fav` DISABLE KEYS */; +INSERT INTO `fav` VALUES (1,773262),(1,1049413),(1,1285016),(1,1375666),(1,1396484),(1,2176165),(1,2194499),(1,2582802),(1,3149038),(1,3659388),(1,8108198),(2,844471),(2,1285016),(2,1375666),(2,1386697),(2,1431045),(2,2096673),(2,2575988),(2,3659388),(2,4425200),(2,7734218),(3,108778),(3,364845),(3,386676),(3,773262),(3,898266),(3,903747),(3,2707408),(4,773262),(4,903747),(4,2176165),(7,322259),(7,903747),(7,4425200),(9,96895),(9,322259),(9,360717),(9,1013752),(9,1905041),(9,2707408),(17,242519),(17,5074352),(18,66763),(18,1285016),(18,1324059),(18,2194499),(18,3148502),(18,5311514),(18,6577798),(18,7838252),(18,10698680); +/*!40000 ALTER TABLE `fav` ENABLE KEYS */; +UNLOCK TABLES; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8 */ ; +/*!50003 SET character_set_results = utf8 */ ; +/*!50003 SET collation_connection = utf8_general_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`may`@`localhost`*/ /*!50003 TRIGGER after_fav_insert +AFTER INSERT +ON fav FOR EACH ROW +BEGIN + update user_info + set recent_fav = ( + select concat(primaryTitle, ' (', startYear, ')') + from title_basics + where tconst=NEW.tconst + ) + where user_id=new.user_id; +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8 */ ; +/*!50003 SET character_set_results = utf8 */ ; +/*!50003 SET collation_connection = utf8_general_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`may`@`localhost`*/ /*!50003 TRIGGER after_fav_insert_fg +AFTER INSERT +ON fav FOR EACH ROW +BEGIN + call set_fav_genre(NEW.user_id); +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!50003 SET @saved_cs_client = @@character_set_client */ ; +/*!50003 SET @saved_cs_results = @@character_set_results */ ; +/*!50003 SET @saved_col_connection = @@collation_connection */ ; +/*!50003 SET character_set_client = utf8 */ ; +/*!50003 SET character_set_results = utf8 */ ; +/*!50003 SET collation_connection = utf8_general_ci */ ; +/*!50003 SET @saved_sql_mode = @@sql_mode */ ; +/*!50003 SET sql_mode = 'STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; +DELIMITER ;; +/*!50003 CREATE*/ /*!50017 DEFINER=`may`@`localhost`*/ /*!50003 TRIGGER after_fav_delete_fg +AFTER DELETE +ON fav FOR EACH ROW +BEGIN + call set_fav_genre(OLD.user_id); +END */;; +DELIMITER ; +/*!50003 SET sql_mode = @saved_sql_mode */ ; +/*!50003 SET character_set_client = @saved_cs_client */ ; +/*!50003 SET character_set_results = @saved_cs_results */ ; +/*!50003 SET collation_connection = @saved_col_connection */ ; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2019-12-13 8:15:24
A mysql/dumps/user_info.sql

@@ -0,0 +1,54 @@

+-- MariaDB dump 10.17 Distrib 10.4.10-MariaDB, for Linux (x86_64) +-- +-- Host: localhost Database: imdb +-- ------------------------------------------------------ +-- Server version 10.4.10-MariaDB + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `user_info` +-- + +DROP TABLE IF EXISTS `user_info`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `user_info` ( + `username` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `password` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `user_id` int(11) NOT NULL AUTO_INCREMENT, + `fav_genre` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `recent_fav` text COLLATE utf8mb4_unicode_ci DEFAULT NULL, + PRIMARY KEY (`user_id`) +) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `user_info` +-- + +LOCK TABLES `user_info` WRITE; +/*!40000 ALTER TABLE `user_info` DISABLE KEYS */; +INSERT INTO `user_info` VALUES ('prithu','sha256$brDWyaok$11ca65c9023e450113223e4b5a53d3063bafbb205d58dc51787a31a7d2f29cea',1,'Drama','Up (2009)'),('debian','sha256$oAEotYlT$5f59e9b701c37f6571763a6b084931e1e19cf0f665f03c887162279f7b2e8c58',2,'Adventure','Whiplash (2014)'),('hello','sha256$Bvqvy5bz$ce769bc586dae53cf756975c3bc931acf7f87e1a92b3447fb174f2f56301d4e6',3,'Crime','NCIS (2003)'),('newuser','sha256$nQOsv4Fc$292144db6227233a5c0364c602d305fab2a75cfb38e3893a3e84e7234c4d74a9',4,NULL,'Another (2012)'),('yet','sha256$sRSlPrjK$7a7d40041d4f037388177a5f9b1d4187276fa1998bba24ca79a743c81de13813',5,NULL,NULL),('newdamn','sha256$E46jWfEo$9cc4efcf73b64b2212b5107da74f53944904ec6cc3d334b6b2828d191ae6a3f8',6,NULL,NULL),('lucy','sha256$7RyeibwV$d2909e0e176f050355c5434c89f1eaf68e9a31d3a4e18bdab8a502fa123c6e58',7,'Crime','Breaking Bad (2008)'),('newuser2','sha256$XP9zqfIM$19c5c22ac37262f05e549a85f00b6fb8edf7139b40f6e61113343f3a9a152e07',8,NULL,NULL),('zomato','sha256$lnMiMpku$ef44a51ba1e5148c58d6476a6475e9dedb753170988124c60e7ef0e3062e92f5',9,'Action','Batman (1989)'),('rahul','sha256$qtHHetb4$5b831d8c06060066eb7c2f3cc1cc2671b99d83394d1730e28543edd759eb7a48',17,'Action','Toy Story 3 (2010)'),('Pacific','sha256$VjTDkNUo$e739e62552bc912352921d1dac75e89c55572016999af811576f3ab7152b11fd',18,'Drama','The Social Network (2010)'),('Prashant','sha256$2VCBhNJ1$1a2f9591b69e07e963f3826ebb191e4a2f771d089e97d2a6d6de8a55b614f566',19,NULL,NULL); +/*!40000 ALTER TABLE `user_info` ENABLE KEYS */; +UNLOCK TABLES; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2019-12-13 8:15:00
A mysql/procedure_triggers/procedure.sql

@@ -0,0 +1,91 @@

+DELIMITER $$ + +DROP PROCEDURE IF EXISTS set_fav_genre $$ +/* This procedure takes a 'user_id' and updates the 'fav_genre' of the useri + depending on the favourite'd movies he has in 'fav' */ +CREATE PROCEDURE set_fav_genre( + IN u_id INT +) +BEGIN + declare fav_genre varchar(100) DEFAULT NULL; + drop temporary table if exists temp_genre; + create temporary table temp_genre( + tconst int, + genres text + ); + + insert into temp_genre(tconst, genres) + select tconst, genres + from title_basics + where tconst in ( + select tconst + from fav + where user_id=u_id + ); + + select genres + into fav_genre + from ( + select count(genres) as c, genres + from ( + + select temp_genre.tconst, + substring_index(substring_index(temp_genre.genres, ',', numbers.n), ',', -1) genres + from ( + select 1 n union all + select 2 union all select 3 union all + select 4 union all select 5 union all + select 6) numbers + INNER JOIN temp_genre + ON + CHAR_LENGTH(temp_genre.genres)-CHAR_LENGTH(REPLACE(temp_genre.genres, ',', ''))>=numbers.n-1 + ORDER by + tconst, n + ) as G1 + group by G1.genres + ) as G2 + order by G2.c desc + limit 1; + + update user_info + set fav_genre = fav_genre + where user_id = u_id; +END $$ +DELIMITER ; + +/* OLD PROCEDURE (USING CURSORS) */ + +/* CREATE PROCEDURE set_fav_genre( */ +/* IN u_id INT */ +/* ) */ +/* BEGIN */ +/* DECLARE finished INT DEFAULT 0; */ +/* DECLARE genre varchar(100) DEFAULT ""; */ + +/* DECLARE curGenre */ +/* CURSOR FOR */ +/* SELECT genres FROM title_basics where tconst in ( */ +/* SELECT tconst from fav where user_id=u_id); */ + +/* DECLARE CONTINUE HANDLER FOR NOT FOUND SET finished = 1; */ +/* TRUNCATE TABLE tmp_store; */ +/* OPEN curGenre; */ +/* getGenre: LOOP */ +/* FETCH curGenre INTO genre; */ +/* if finished = 1 then */ +/* leave getGenre; */ +/* END IF; */ +/* call split_string(genre); */ +/* end loop getGenre; */ +/* close curGenre; */ +/* update user_info */ +/* set fav_genre = ( */ +/* select allValues from ( */ +/* select count(allValues) as c, allValues */ +/* from tmp_store */ +/* group by allValues) as line */ +/* order by c desc */ +/* limit 1) */ +/* where user_id=u_id; */ +/* END$$ */ +/* DELIMITER ; */
A mysql/procedure_triggers/split.sql

@@ -0,0 +1,26 @@

+DELIMITER $$ +DROP PROCEDURE IF EXISTS split_string $$ +CREATE TABLE if not exists tmp_store ( + id int NOT NULL AUTO_INCREMENT, + allValues varchar(100) Default NULL, + PRIMARY KEY(id) +) $$ + +CREATE PROCEDURE split_string(Value longtext) +BEGIN +DECLARE front TEXT DEFAULT NULL; +DECLARE frontlen INT DEFAULT NULL; +DECLARE TempValue TEXT DEFAULT NULL; +iterator: +LOOP +IF LENGTH(TRIM(Value)) = 0 OR Value IS NULL THEN +LEAVE iterator; +END IF; +SET front = SUBSTRING_INDEX(Value,',',1); +SET frontlen = LENGTH(front); +SET TempValue = TRIM(front); +INSERT INTO tmp_store (allValues) VALUES (TempValue); +SET Value = INSERT(Value,1,frontlen + 1,''); +END LOOP; +END $$ +DELIMITER ;
A mysql/procedure_triggers/split2.sql

@@ -0,0 +1,38 @@

+/* Given : + +----------------------------+ + | genres | + +----------------------------+ + | Action, Adventure | + +----------------------------+ + | Comedy, Drama | + +----------------------------+ + | Action, Romance | + +----------------------------+ + | Comedy, Mystery, Adventure | + ------------------------------ + + we should be able to produce a list like this: + Action + Adventure + Comedy + Drama + Action + Romance + Comedy + Mystery + Adventure + + and this is what this query does + (split.sql does the same in a different way) +*/ +select temp_genre.tconst, substring_index(substring_index(temp_genre.genres, ',', numbers.n), ',', -1) genres +from ( + select 1 n union all + select 2 union all select 3 union all + select 4 union all select 5 union all + select 6) numbers +INNER JOIN temp_genre +ON +CHAR_LENGTH(temp_genre.genres)-CHAR_LENGTH(REPLACE(temp_genre.genres, ',', ''))>=numbers.n-1 +ORDER by +tconst, n
A mysql/procedure_triggers/trigger_fav_genre.sql

@@ -0,0 +1,22 @@

+/* After a user adds a favorite movie or after he deletes one, + update his fav_genre +*/ + +DELIMITER $$ + +DROP TRIGGER IF EXISTS after_fav_insert_fg $$ +CREATE TRIGGER after_fav_insert_fg +AFTER INSERT +ON fav FOR EACH ROW +BEGIN + call set_fav_genre(NEW.user_id); +END $$ + +DROP TRIGGER IF EXISTS after_fav_delete_fg $$ +CREATE TRIGGER after_fav_delete_fg +AFTER DELETE +ON fav FOR EACH ROW +BEGIN + call set_fav_genre(OLD.user_id); +END $$ +DELIMITER ;
A mysql/procedure_triggers/trigger_recent_fav.sql

@@ -0,0 +1,18 @@

+/* recent_fav is exactly what it sounds like - its the recent most + favourited movie of the user. +*/ +DELIMITER $$ +DROP TRIGGER IF EXISTS after_fav_insert $$ +CREATE TRIGGER after_fav_insert +AFTER INSERT +ON fav FOR EACH ROW +BEGIN + update user_info + set recent_fav = ( + select concat(primaryTitle, ' (', startYear, ')') + from title_basics + where tconst=NEW.tconst + ) + where user_id=new.user_id; +END $$ +DELIMITER ;
A requirements.txt

@@ -0,0 +1,7 @@

+flask +flask-cors +flask-sqlalchemy +imdbpy +tmdbsimple +gunicorn +psycopg2
A runtime.txt

@@ -0,0 +1,1 @@

+python-3.8.1