diff --git a/App/controllers/testingfunctions.py b/App/controllers/testingfunctions.py new file mode 100644 index 000000000..39c300c11 --- /dev/null +++ b/App/controllers/testingfunctions.py @@ -0,0 +1,58 @@ +from googleapiclient.discovery import build +from googleapiclient.errors import HttpError + +apiKey = 'AIzaSyAa3sah23VK9X4r0hxsC4xHvWTUeLurpv8' +youtube = build('youtube', 'v3', developerKey=apiKey) + +class Video: + def __init__(self, title, image_url, video_url, description, video_id): + self.title = title + self.image_url = image_url + self.video_url = video_url + self.description = description + self.video_id = video_id + + +def get_video_url(video_id): + return f"https://www.youtube.com/watch?v={video_id}" + +def search_by_keyword(api_key, keyword, max_results): + youtube = build("youtube", "v3", developerKey=api_key) + + try: + request = youtube.search().list( + part="id,snippet", + q=keyword, + maxResults=max_results + ) + response = request.execute() + videos = [] + if "items" in response: + for item in response["items"]: + if item["id"].get("kind") == "youtube#video": + video_id = item["id"]["videoId"] + video_url = get_video_url(video_id) + title = item["snippet"]["title"] + image = item["snippet"]["thumbnails"]["default"]["url"] + description = item["snippet"]["description"] + video = Video(title, image, video_url, description, video_id) + videos.append(video) + else: + print("This item is not a video.") + else: + print("No videos found.") + + except HttpError as e: + print("An HTTP error occurred:") + print(e.content) + + return videos + +def home(): + videos = search_by_keyword("AIzaSyAa3sah23VK9X4r0hxsC4xHvWTUeLurpv8", "fitness", 30) + print(videos) + +home() + + + diff --git a/App/main.py b/App/main.py index 5fdc15cc2..63144cf0b 100644 --- a/App/main.py +++ b/App/main.py @@ -4,6 +4,8 @@ from flask_cors import CORS from werkzeug.utils import secure_filename from werkzeug.datastructures import FileStorage +from googleapiclient.discovery import build +from googleapiclient.errors import HttpError from App.database import init_db from App.config import load_config @@ -15,6 +17,14 @@ from App.views import views +class Video: + def __init__(self, title, image_url, video_url, description, video_id): + self.title = title + self.image_url = image_url + self.video_url = video_url + self.description = description + self.video_id = video_id + def add_views(app): for view in views: app.register_blueprint(view) @@ -38,3 +48,45 @@ def custom_unauthorized_response(error): app.app_context().push() return app +def get_video_url(video_id): + return f"https://www.youtube.com/watch?v={video_id}" + +def search_by_keyword(api_key, keyword, max_results): + youtube = build("youtube", "v3", developerKey=api_key) + + try: + request = youtube.search().list( + part="id,snippet", + q=keyword, + maxResults=max_results + ) + response = request.execute() + videos = [] + if "items" in response: + for item in response["items"]: + if item["id"].get("kind") == "youtube#video": + video_id = item["id"]["videoId"] + video_url = get_video_url(video_id) + title = item["snippet"]["title"] + image = item["snippet"]["thumbnails"]["default"]["url"] + description = item["snippet"]["description"] + video = Video(title, image, video_url, description, video_id) + videos.append(video) + else: + print("This item is not a video.") + else: + print("No videos found.") + + except HttpError as e: + print("An HTTP error occurred:") + print(e.content) + + return videos + +app = create_app() # Create the app object + +@app.route('/home') +def home(): + videos = search_by_keyword("AIzaSyAa3sah23VK9X4r0hxsC4xHvWTUeLurpv8", "fitness", 30) + return render_template('index.html', videos=videos) + diff --git a/App/models/user.py b/App/models/user.py index 8efe083ab..db93ce100 100644 --- a/App/models/user.py +++ b/App/models/user.py @@ -5,6 +5,9 @@ class User(db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String, nullable=False, unique=True) password = db.Column(db.String(120), nullable=False) + height = db.Column(db.Double, nullable=True) + weight = db.Column(db.Double, nullable=True) + videos = db.relationship('Video', backref='user', lazy=True, cascade="all, delete-orphan") def __init__(self, username, password): self.username = username @@ -24,3 +27,22 @@ def check_password(self, password): """Check hashed password.""" return check_password_hash(self.password, password) +class Video(db.Model): + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + title = db.Column(db.String(120)) + image = db.Column(db.String(120)) + description = db.Column(db.String(120)) + url = db.Column(db.String(120)) + + def __init__(self, title, image, description, url, user_id): + self.title = title + self.image = image + self.description = description + self.url = url + self.user_id = user_id + + + + + diff --git a/App/static/style.css b/App/static/style.css index e69de29bb..97de86a21 100644 --- a/App/static/style.css +++ b/App/static/style.css @@ -0,0 +1,148 @@ +/* Index CSS */ +.search-bar { + margin-bottom: 10px; + overflow: hidden; +} + +.search-bar input[type="text"] { + + float: left; +} + +.search-bar button { + float: right; +} + +/* Side menu (index.html) */ +.vertical-menu { + /* Your side menu styling */ + width: 200px; + float: left; + background-color: #f8f9fa; + border-right: 1px solid #ccc; +} + +.vertical-menu a { + display: block; + padding: 10px; + text-decoration: none; +} + +/* active link (index.html) */ +.vertical-menu a.active { + background-color: #ccc; +} + +/* Grid menu contents (index.html) */ +.grid-container { + overflow: hidden; +} + +.grid-item { + + background-color: #f0f0f0; + padding: 20px; + text-align: center; + float: left; + width: calc(33.33% - 20px); + margin: 10px; + box-sizing: border-box; +} + +/* Logo CSS*/ + + +/*Profile CSS*/ + +.emp-profile{ + padding: 3%; + margin-top: 3%; + margin-bottom: 3%; + border-radius: 0.5rem; + background: #fff; +} +.profile-img{ + text-align: center; +} +.profile-img img{ + width: 70%; + height: 100%; +} +.profile-img .file { + position: relative; + overflow: hidden; + margin-top: -20%; + width: 70%; + border: none; + border-radius: 0; + font-size: 15px; + background: #212529b8; +} +.profile-img .file input { + position: absolute; + opacity: 0; + right: 0; + top: 0; +} +.profile-head h5{ + color: #333; +} +.profile-head h6{ + color: #0062cc; +} +.profile-edit-btn{ + border: none; + border-radius: 1.5rem; + width: 70%; + padding: 2%; + font-weight: 600; + color: #6c757d; + cursor: pointer; +} +.proile-rating{ + font-size: 12px; + color: #818182; + margin-top: 5%; +} +.proile-rating span{ + color: #495057; + font-size: 15px; + font-weight: 600; +} +.profile-head .nav-tabs{ + margin-bottom:5%; +} +.profile-head .nav-tabs .nav-link{ + font-weight:600; + border: none; +} +.profile-head .nav-tabs .nav-link.active{ + border: none; + border-bottom:2px solid #0062cc; +} +.profile-work{ + padding: 14%; + margin-top: -15%; +} +.profile-work p{ + font-size: 12px; + color: #818182; + font-weight: 600; + margin-top: 10%; +} +.profile-work a{ + text-decoration: none; + color: #495057; + font-weight: 600; + font-size: 14px; +} +.profile-work ul{ + list-style: none; +} +.profile-tab label{ + font-weight: 600; +} +.profile-tab p{ + font-weight: 600; + color: #0062cc; +} \ No newline at end of file diff --git a/App/templates/index.html b/App/templates/index.html index 96ec19670..17cb83714 100644 --- a/App/templates/index.html +++ b/App/templates/index.html @@ -2,12 +2,50 @@ {% block title %}Flask MVC App{% endblock %} {% block page %}Flask MVC App{% endblock %} -{{ super() }} - {% block content %} -

Flask MVC

+

TBD Workout Planner

{% if is_authenticated %} -

Welcome {{current_user.username}}

+

Welcome {{ current_user.username }}

{% endif %} -

This is a boileplate flask application which follows the MVC pattern for structuring the project.

-{% endblock %} \ No newline at end of file +

This is a boilerplate Flask application which follows the MVC pattern for structuring the project.

+ + + +
+ + +
+ + {% for video in videos %} +
+
+
+ +
+
+
{{ video.title }}
+

{{ video.description }}

+ Watch Video + {% if is_authenticated %} +
+ + + + + +
+ {%endif%} +
+
+
+ {% endfor %} +
+
+{% endblock %} diff --git a/App/templates/layout.html b/App/templates/layout.html index ae01fe223..fcd551faf 100644 --- a/App/templates/layout.html +++ b/App/templates/layout.html @@ -1,6 +1,6 @@ - + @@ -21,13 +21,14 @@
  • Home
  • Users Jinja
  • {% if is_authenticated %} -
  • Identify
  • +
  • Profile
  • {% endif %}
  • Users JS
  • {% if is_authenticated %} {% else %}
    -
    - -
    -
    - - -
    -
    - - -
    + +
    +
    +
    + Create Account + +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    + +
    +
    + + +
    + +
    +
    + +
    +
    + +
    +
    + -
    -
    - -
    -
    +
    diff --git a/App/views/index.py b/App/views/index.py index cff58abd9..d98d83f10 100644 --- a/App/views/index.py +++ b/App/views/index.py @@ -1,12 +1,116 @@ -from flask import Blueprint, redirect, render_template, request, send_from_directory, jsonify -from App.models import db +from flask import Blueprint, render_template, send_from_directory, jsonify, session +from flask import request, redirect, url_for +from App.models import db, User, Video from App.controllers import create_user +from googleapiclient.discovery import build +from googleapiclient.errors import HttpError +from flask_jwt_extended import JWTManager, jwt_required, create_access_token, get_jwt_identity +from flask import flash + + index_views = Blueprint('index_views', __name__, template_folder='../templates') +class Videos: + def __init__(self, title, image_url, video_url, description, video_id): + self.title = title + self.image_url = image_url + self.video_url = video_url + self.description = description + self.video_id = video_id + +def get_video_url(video_id): + return f"https://www.youtube.com/watch?v={video_id}" + +def search_by_keyword(api_key, keyword, max_results): + youtube = build("youtube", "v3", developerKey=api_key) + + try: + request = youtube.search().list( + part="id,snippet", + q=keyword, + maxResults=max_results + ) + response = request.execute() + videos = [] + if "items" in response: + for item in response["items"]: + if item["id"].get("kind") == "youtube#video": + video_id = item["id"]["videoId"] + video_url = get_video_url(video_id) + title = item["snippet"]["title"] + image = item["snippet"]["thumbnails"]["default"]["url"] + description = item["snippet"]["description"] + video = Videos(title, image, video_url, description, video_id) + videos.append(video) + else: + print("This item is not a video.") + else: + print("No videos found.") + + except HttpError as e: + print("An HTTP error occurred:") + print(e.content) + + return videos + @index_views.route('/', methods=['GET']) def index_page(): - return render_template('index.html') + videos = search_by_keyword("AIzaSyAa3sah23VK9X4r0hxsC4xHvWTUeLurpv8", "fitness", 30) + return render_template('index.html', videos=videos) + +@index_views.route('/search', methods=['GET']) +def search(): + keyword = request.args.get('keyword') + if keyword: + videos = search_by_keyword("AIzaSyAa3sah23VK9X4r0hxsC4xHvWTUeLurpv8", keyword, 39) + return render_template('index.html', videos=videos) + +@index_views.route('/add', methods=['POST']) +@jwt_required() +def add(): + url = request.form.get('url') + existing_video = Video.query.filter_by(url=url, user_id=get_jwt_identity()).first() + if existing_video: + flash("This video is already in your playlist.") + return redirect(url_for('index_views.index_page')) + + image = request.form.get('image') + description = request.form.get('description') + title = request.form.get('title') + user = get_jwt_identity() + current_user = User.query.get(user) + + video = Video(title=title, image=image, url=url, description=description, user_id=current_user.id) + current_user.videos.append(video) + + db.session.add(current_user) + db.session.commit() + return redirect(url_for('index_views.index_page')) + + + + +@index_views.route('/playlist') +@jwt_required() +def playlist(): + user = get_jwt_identity() + current_user = User.query.get(user) + videos = Video.query.filter_by(user_id=current_user.id).all() + return render_template('playlist.html', videos=videos) + + + +@index_views.route('/delete', methods=['POST', 'DELETE']) +@jwt_required() +def delete(): + user = get_jwt_identity() + current_user = User.query.get(user) + video_id = request.form.get('id') + video = Video.query.filter_by(id=video_id, user_id=current_user.id).first() + db.session.delete(video) + db.session.commit() + return redirect(url_for('index_views.playlist')) @index_views.route('/init', methods=['GET']) def init(): @@ -17,4 +121,9 @@ def init(): @index_views.route('/health', methods=['GET']) def health_check(): - return jsonify({'status':'healthy'}) \ No newline at end of file + return jsonify({'status':'healthy'}) + + + + + diff --git a/flaskmvc.code-workspace b/flaskmvc.code-workspace new file mode 100644 index 000000000..876a1499c --- /dev/null +++ b/flaskmvc.code-workspace @@ -0,0 +1,8 @@ +{ + "folders": [ + { + "path": "." + } + ], + "settings": {} +} \ No newline at end of file