diff --git a/server/api/.env b/server/api/.env index c246951..5f3a8e5 100644 --- a/server/api/.env +++ b/server/api/.env @@ -1,4 +1,5 @@ MOVIE_API_HOST=0.0.0.0 MOVIE_API_PORT=5000 OPENWEATHER_API_KEY=9291dbdcabf7c26431129cb4438c30e1 +JWT_SECRET_KEY=2df599ec6fbe45e48a0f8e0ba2a4d247 diff --git a/server/api/app.py b/server/api/app.py index 1412be4..7d4689a 100755 --- a/server/api/app.py +++ b/server/api/app.py @@ -1,28 +1,30 @@ #!/usr/bin/env python3 """ Starts the Flask web app """ import os - import requests from dotenv import load_dotenv from flask import Flask, jsonify, request from models import storage from api.views import app_views +from api.views.auth import jwt from flask_cors import CORS +from datetime import timedelta load_dotenv() app = Flask(__name__) +jwt.init_app(app) CORS(app) app.url_map.strict_slashes = False app.register_blueprint(app_views) +app.config['JWT_SECRET_KEY'] = os.getenv('JWT_SECRET_KEY') +app.config['JWT_ACCESS_TOKEN_EXPIRES'] = timedelta(hours=24) HOST = "0.0.0.0" PORT = 5000 -SECRET_KEY = os.getenv('SECRET_KEY') - - OPENWEATHER_API_KEY = os.getenv('OPENWEATHER_API_KEY') +# Weather route to be moved to api.views.weather def get_weather(lat, lon): base_url = "https://api.openweathermap.org/data/2.5/weather" params = { @@ -41,6 +43,7 @@ def get_weather(lat, lon): return None # Return None if the request failed +# Weather route to be moved to api.views.weather @app.route('/weather', methods=['GET']) def weather(): lat = request.args.get('lat') @@ -59,6 +62,7 @@ def weather(): return jsonify({'error': 'Failed to fetch weather data'}), 500 +# To be removed att deployement @app.route('/volumes') def volume(): """ A dummy route to test volumes of docker""" diff --git a/server/api/views/__init__.py b/server/api/views/__init__.py index 42dca5b..6e1bc5b 100755 --- a/server/api/views/__init__.py +++ b/server/api/views/__init__.py @@ -5,3 +5,5 @@ app_views = Blueprint('app_views', __name__, url_prefix="/api") from api.views.index import * # nopep8 +from api.views.auth import * # nopep8 +from api.views.users import * # nopep8 diff --git a/server/api/views/auth.py b/server/api/views/auth.py new file mode 100644 index 0000000..47dcd2a --- /dev/null +++ b/server/api/views/auth.py @@ -0,0 +1,66 @@ +#!/usr/bin/python3 +"""Auth routes""" +from api.views import app_views +from flask import jsonify, request +from flask_jwt_extended import JWTManager, create_access_token +from models import storage +from models.user import User + + +jwt = JWTManager() + + +@jwt.user_identity_loader +def user_identity_lookup(user): + return user.id + + +@jwt.user_lookup_loader +def user_lookup_callback(_jwt_header, jwt_data): + identity = jwt_data["sub"] + return storage.get(User, identity) + + +@app_views.route('/register', methods=['POST']) +def register_user(): + """Register a new user""" + data = request.get_json() + if not data: + return jsonify({'error': 'No data provided'}), 400 + + email = data.get('email') + password = data.get('password') + first_name = data.get('first_name') + last_name = data.get('last_name') + + if not email or not password or not first_name or not last_name: + return jsonify({'error': 'Missing required fields'}), 400 + + if storage.get_specific(User, 'email', email): + return jsonify({'error': 'User already exists'}), 400 + + new_user = User(**data) + + storage.new(new_user) + storage.save() + + return jsonify(new_user.to_dict()), 201 + + +@app_views.route('/login', methods=['POST']) +def login(): + """Sign in an user""" + data = request.get_json() + if not data or 'email' not in data or 'password' not in data: + return jsonify({'error': 'Missing email or password'}), 400 + + email = data['email'] + password = data['password'] + + user = storage.get_specific(User, 'email', email) + + if not user or user.verify_password(password, user.password) is False: + return jsonify({'error': 'Invalid email or password'}), 401 + + token = create_access_token(identity=user) + return jsonify({'token': token}) diff --git a/server/api/views/users.py b/server/api/views/users.py new file mode 100644 index 0000000..f281f8e --- /dev/null +++ b/server/api/views/users.py @@ -0,0 +1,22 @@ +#!/usr/bin/python3 +"""Users related routes""" +from api.views import app_views +from flask import jsonify, request +from flask_jwt_extended import get_jwt_identity, jwt_required +from models.user import User +from models import storage + + +@app_views.route('/users/profile', methods=['GET']) +@jwt_required() +def get_user_profile(): + """Get user profile""" + try: + current_user = get_jwt_identity() + except Exception as e: + return jsonify({"error": "Invalid token"}), 401 + + user_profile = storage.get_specific(User, 'id', current_user) + if not user_profile: + return jsonify({"error": "User not found"}), 404 + return jsonify(user_profile.to_dict()) diff --git a/server/flask.dockerfile b/server/flask.dockerfile index 48ac92a..5475e1a 100644 --- a/server/flask.dockerfile +++ b/server/flask.dockerfile @@ -9,7 +9,7 @@ WORKDIR /movie_name COPY . /movie_name -RUN pip3 install flask==2.1.0 werkzeug==2.1.1 flask-cors==4.0.1 sqlalchemy==1.4.22 mysqlclient==2.2.4 python-dotenv requests +RUN pip3 install flask==2.1.0 werkzeug==2.1.1 flask-cors==4.0.1 sqlalchemy==1.4.22 mysqlclient==2.2.4 python-dotenv requests flask-jwt-extended RUN apt-get clean && rm -rf /var/lib/apt/lists/* diff --git a/server/models/engine/db_storage.py b/server/models/engine/db_storage.py index c35f482..b80d741 100755 --- a/server/models/engine/db_storage.py +++ b/server/models/engine/db_storage.py @@ -3,7 +3,7 @@ Contains the class DBStorage """ -from models.base_model import BaseModel, Base +from models.base_model import Base from models.movie import Movie from models.user_movie import User_Movie from models.user import User @@ -69,11 +69,12 @@ def close(self): def get(self, cls, id): """Returns the object based on the class and its ID, or None if not found""" - objs = self.all(cls).values() - for obj in objs: - if obj.id == id: - return obj - return None + return self.__session.query(cls).filter(getattr(cls, 'id') == id) + + def get_specific(self, cls, attribute, value): + """Retun the object based on the class, attrubite and value""" + return self.__session.query(cls).filter( + getattr(cls, attribute) == value).first() def count(self, cls=None): """ diff --git a/server/models/user.py b/server/models/user.py index 55d7525..69e7be3 100755 --- a/server/models/user.py +++ b/server/models/user.py @@ -23,3 +23,12 @@ def __init__(self, *args, **kwargs): kwargs["password"] = hashlib.sha256( kwargs["password"].encode()).hexdigest() super().__init__(*args, **kwargs) + + @staticmethod + def verify_password(plain_password, hashed_password): + """Verify password""" + plain_password_hash = hashlib.sha256( + plain_password.encode()).hexdigest() + if plain_password_hash == hashed_password: + return True + return False