diff --git a/.vscode/launch.json b/.vscode/launch.json index 339b284..4cd06d6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,5 +1,22 @@ { "configurations": [ + { + "name": "Python: Flask", + "type": "python", + "request": "launch", + "module": "flask", + "env": { + "FLASK_APP": "main.py", + "FLASK_DEBUG": "1" + }, + "args": [ + "run", + "--no-debugger", + "--no-reload" + ], + "jinja": true, + "justMyCode": true + }, { "name": "Docker: Python - Flask", "type": "docker", diff --git a/.vscode/settings.json b/.vscode/settings.json index aa3291f..b4787d6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,10 +2,12 @@ "editor.mouseWheelZoom": true, "python.linting.pylintEnabled": true, "python.linting.enabled": true, + "python.formatting.provider": "autopep8", + "python.linting.lintOnSave": true, + "python.languageServer": "Pylance", "[python]": { "editor.wordBasedSuggestions": false, - "editor.defaultFormatter": "ms-python.python", - "editor.formatOnSave": true + "editor.formatOnSave": true, }, "files.exclude": { "**/.git": true, diff --git a/auth/__init__.py b/auth/__init__.py new file mode 100644 index 0000000..5d11ac4 --- /dev/null +++ b/auth/__init__.py @@ -0,0 +1,9 @@ +""" + auth module +""" + +from auth.blueprint import blueprint as auth_blueprint +from auth.decorator import login_required +from auth.function import GetUser + +__all__ = [auth_blueprint, login_required, GetUser] diff --git a/blueprints/auth.py b/auth/blueprint.py similarity index 70% rename from blueprints/auth.py rename to auth/blueprint.py index fec0ba8..28c5006 100644 --- a/blueprints/auth.py +++ b/auth/blueprint.py @@ -6,14 +6,15 @@ import os from flask import Blueprint, request +from jwt_wrapper.service import JwtService from marshmallow import Schema, ValidationError, fields, validate -from services.auth.decorator import login_required -from services.auth.function import GetUser -from services.jwt_wrapper.jwt_wrapper import JwtWrapper -from services.user.user import UserService +from user.service import UserService from util.validate_request import ValidateRequest -bp = Blueprint("auth", __name__, url_prefix="/auth") +from auth.decorator import login_required +from auth.function import GetUser + +blueprint = Blueprint("auth", __name__, url_prefix="/auth") class LoginDto(Schema): @@ -28,7 +29,7 @@ class RegisterDto(LoginDto): pass -@bp.route('/login', methods=['POST']) +@blueprint.route('/login', methods=['POST']) def login(): data = ValidateRequest(LoginDto, request) check = UserService.login(data.email, data.password) @@ -38,28 +39,28 @@ def login(): jwt = {"user_id": user.id} token: str if os.getenv("ENV") != "production": - token = JwtWrapper.encode(jwt, 86400) # 1 day + token = JwtService.encode(jwt, 86400) # 1 day else: - token = JwtWrapper.encode(jwt, 60*5) # 5 minutes + token = JwtService.encode(jwt, 60*5) # 5 minutes return {'token': token}, 200 return {"message": "Invalid login!"}, 401 -@bp.route('/register', methods=['POST']) +@blueprint.route('/register', methods=['POST']) def register(): data = ValidateRequest(LoginDto, request) UserService.register(data.email, data.password) return {'message': "Successfully registered"}, 201 -@bp.route('/user', methods=['GET']) +@blueprint.route('/user', methods=['GET']) @login_required def user(): user = GetUser() return user.email -@bp.errorhandler(Exception) +@blueprint.errorhandler(Exception) def error_handle(err: Exception): if err.__class__ is ValidationError: return str(err), 400 diff --git a/services/auth/decorator.py b/auth/decorator.py similarity index 75% rename from services/auth/decorator.py rename to auth/decorator.py index aaea0e7..2b24a87 100644 --- a/services/auth/decorator.py +++ b/auth/decorator.py @@ -5,8 +5,8 @@ from functools import wraps from flask import request -from services.jwt_wrapper.jwt_wrapper import JwtWrapper -from services.user.user import UserService +from jwt_wrapper.service import JwtService +from user.service import UserService def login_required(f): @@ -17,10 +17,10 @@ def decorated_function(*args, **kwargs): return {"message": "Unauthorized!"}, 401 jwt_token = auth_header.split("Bearer ")[1] - check = JwtWrapper.validate(jwt_token) + check = JwtService.validate(jwt_token) if check: - decoded = JwtWrapper.decode(jwt_token) + decoded = JwtService.decode(jwt_token) user = UserService.find_by_id(decoded["user_id"]) request.user = user return f(*args, **kwargs) diff --git a/services/auth/function.py b/auth/function.py similarity index 75% rename from services/auth/function.py rename to auth/function.py index 4192da4..b0ffcb9 100644 --- a/services/auth/function.py +++ b/auth/function.py @@ -3,7 +3,7 @@ """ from flask import request -from services.user.user import User +from user.model import User def GetUser() -> User: diff --git a/bcrypt_wrapper/__init__.py b/bcrypt_wrapper/__init__.py new file mode 100644 index 0000000..218b35f --- /dev/null +++ b/bcrypt_wrapper/__init__.py @@ -0,0 +1,7 @@ +""" + bcrypt wrapper module +""" + +from bcrypt_wrapper.service import BcryptService + +__all__ = [BcryptService] diff --git a/services/bcrypt_wrapper/bcrypt_wrapper.py b/bcrypt_wrapper/service.py similarity index 82% rename from services/bcrypt_wrapper/bcrypt_wrapper.py rename to bcrypt_wrapper/service.py index fd72ed8..43be03f 100644 --- a/services/bcrypt_wrapper/bcrypt_wrapper.py +++ b/bcrypt_wrapper/service.py @@ -8,13 +8,13 @@ import bcrypt -class BcryptWrapper: +class BcryptService: __salt_round = os.getenv("BCRYPT_SALT") or 10 __salt = bcrypt.gensalt(rounds=__salt_round) @staticmethod def hash(value: str): - return bcrypt.hashpw(value.encode("utf-8"), BcryptWrapper.__salt) + return bcrypt.hashpw(value.encode("utf-8"), BcryptService.__salt) @staticmethod def validate(value: str, hash: str): diff --git a/blueprints/__init__.py b/blueprints/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/car/__init__.py b/car/__init__.py new file mode 100644 index 0000000..edefdf6 --- /dev/null +++ b/car/__init__.py @@ -0,0 +1,9 @@ +""" + car module +""" + +from car.blueprint import blueprint as car_blueprint +from car.model import Car +from car.service import CarService + +__all__ = [Car, car_blueprint, CarService] diff --git a/blueprints/car.py b/car/blueprint.py similarity index 72% rename from blueprints/car.py rename to car/blueprint.py index f17ec3e..0acb831 100644 --- a/blueprints/car.py +++ b/car/blueprint.py @@ -3,17 +3,18 @@ ''' -from http.client import BAD_REQUEST, CREATED, FORBIDDEN, NOT_FOUND, OK -from xmlrpc.client import INTERNAL_ERROR +from http.client import (BAD_REQUEST, CREATED, FORBIDDEN, + INTERNAL_SERVER_ERROR, NOT_FOUND, OK) +from auth.decorator import login_required +from auth.function import GetUser from flask import Blueprint, request from marshmallow import Schema, ValidationError, fields, validate -from services.auth.decorator import login_required -from services.auth.function import GetUser -from services.car.car import CarService from util.validate_request import ValidateRequest -bp = Blueprint("car", __name__, url_prefix="/car") +from car.service import CarService + +blueprint = Blueprint("car", __name__, url_prefix="/car") class CarAddDto(Schema): @@ -31,7 +32,7 @@ class CarDeleteDto(Schema): car_id = fields.Int(required=True) -@bp.route('/add', methods=['POST']) +@blueprint.route('/add', methods=['POST']) @login_required def add_car(): user = GetUser() @@ -40,7 +41,7 @@ def add_car(): return {"message": "Car add."}, CREATED -@bp.route('/remove', methods=['DELETE']) +@blueprint.route('/remove', methods=['DELETE']) @login_required def remove_car(): user = GetUser() @@ -55,8 +56,8 @@ def remove_car(): return {"message": "Car not found!"}, NOT_FOUND -@bp.errorhandler(Exception) +@blueprint.errorhandler(Exception) def error_handle(err: Exception): if err.__class__ is ValidationError: return str(err), BAD_REQUEST - return {'message': "Internal server exception!", "error": str(err)}, INTERNAL_ERROR + return {'message': "Internal server exception!"}, INTERNAL_SERVER_ERROR diff --git a/car/model.py b/car/model.py new file mode 100644 index 0000000..7b6a9a7 --- /dev/null +++ b/car/model.py @@ -0,0 +1,19 @@ +from database import Base +from sqlalchemy import Column, Integer, String + + +class Car(Base): + __tablename__ = 'cars' + + id = Column(Integer, primary_key=True) + car_license_plate = Column(String(100), unique=True) + car_owner_id = Column(Integer()) + car_type = Column(String(100)) + + def __init__(self, car_license_plate: str, car_owner_id: str, car_type: str): + self.car_license_plate = car_license_plate + self.car_owner_id = car_owner_id + self.car_type = car_type + + def __repr__(self): + return f'' diff --git a/services/car/car.py b/car/service.py similarity index 62% rename from services/car/car.py rename to car/service.py index 9001f14..a1c91c0 100644 --- a/services/car/car.py +++ b/car/service.py @@ -1,24 +1,8 @@ -from services.database import Base, db_session -from services.user.user import User -from sqlalchemy import Column, Integer, String +from database.database import db_session from sqlalchemy.exc import IntegrityError +from user.model import User - -class Car(Base): - __tablename__ = 'cars' - - id = Column(Integer, primary_key=True) - car_license_plate = Column(String(100), unique=True) - car_owner_id = Column(Integer()) - car_type = Column(String(100)) - - def __init__(self, car_license_plate: str, car_owner_id: str, car_type: str): - self.car_license_plate = car_license_plate - self.car_owner_id = car_owner_id - self.car_type = car_type - - def __repr__(self): - return f'' +from car.model import Car class CarService: diff --git a/database/__init__.py b/database/__init__.py new file mode 100644 index 0000000..ffe5ae9 --- /dev/null +++ b/database/__init__.py @@ -0,0 +1,5 @@ +""" + database module +""" + +from database.database import * diff --git a/services/database.py b/database/database.py similarity index 100% rename from services/database.py rename to database/database.py diff --git a/jwt_wrapper/__init__.py b/jwt_wrapper/__init__.py new file mode 100644 index 0000000..8d0bbdb --- /dev/null +++ b/jwt_wrapper/__init__.py @@ -0,0 +1,7 @@ +""" + jwt wrapper module +""" + +from jwt_wrapper.service import JwtService + +__all__ = [JwtService] diff --git a/services/jwt_wrapper/jwt_wrapper.py b/jwt_wrapper/service.py similarity index 70% rename from services/jwt_wrapper/jwt_wrapper.py rename to jwt_wrapper/service.py index 8e2c7ec..24594a2 100644 --- a/services/jwt_wrapper/jwt_wrapper.py +++ b/jwt_wrapper/service.py @@ -9,20 +9,20 @@ import jwt -class JwtWrapper: +class JwtService: __jwt_secret = os.getenv("BCRYPT_SALT") or "dev-secret" def encode(value: any, duration: int): value["iat"] = datetime.now(tz=timezone.utc) value["exp"] = value["iat"] + timedelta(seconds=duration) - return jwt.encode(value, JwtWrapper.__jwt_secret, "HS256") + return jwt.encode(value, JwtService.__jwt_secret, "HS256") def validate(value: str): try: - jwt.decode(value, JwtWrapper.__jwt_secret, algorithms=["HS256"]) + jwt.decode(value, JwtService.__jwt_secret, algorithms=["HS256"]) return True except: return False def decode(value: str): - return jwt.decode(value, JwtWrapper.__jwt_secret, algorithms=["HS256"]) + return jwt.decode(value, JwtService.__jwt_secret, algorithms=["HS256"]) diff --git a/main.py b/main.py index 80e4751..4039868 100644 --- a/main.py +++ b/main.py @@ -5,9 +5,9 @@ from dotenv import load_dotenv from flask import Flask -import blueprints.auth as auth -import blueprints.car as car -from services.database import db_session, init_db +from auth import auth_blueprint +from car import car_blueprint +from database.database import db_session, init_db load_dotenv() @@ -15,8 +15,8 @@ app = Flask(__name__) -app.register_blueprint(auth.bp) -app.register_blueprint(car.bp) +app.register_blueprint(auth_blueprint) +app.register_blueprint(car_blueprint) @app.teardown_appcontext diff --git a/mock/mock.py b/mock/mock.py index 5125e1d..65062c6 100644 --- a/mock/mock.py +++ b/mock/mock.py @@ -9,9 +9,9 @@ if __name__ == '__main__': - from services.car.car import CarService - from services.database import Base, db_session, engine, init_db - from services.user.user import UserService + from car.service import CarService + from database.database import Base, db_session, engine, init_db + from user.service import UserService # drop all Base.metadata.drop_all(bind=engine) diff --git a/services/__init__.py b/services/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/services/auth/__init__.py b/services/auth/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/services/bcrypt_wrapper/__init__.py b/services/bcrypt_wrapper/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/services/car/__init__.py b/services/car/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/services/jwt_wrapper/__init__.py b/services/jwt_wrapper/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/services/user/__init__.py b/services/user/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/thunder-tests/thunderclient.json b/thunder-tests/thunderclient.json index b7d9594..a38e93f 100644 --- a/thunder-tests/thunderclient.json +++ b/thunder-tests/thunderclient.json @@ -79,7 +79,7 @@ "method": "DELETE", "sortNum": 20000, "created": "2022-09-19T13:00:30.444Z", - "modified": "2022-09-19T13:13:57.167Z", + "modified": "2022-09-19T18:45:43.093Z", "headers": [], "params": [], "body": { diff --git a/user/__init__.py b/user/__init__.py new file mode 100644 index 0000000..4e70bb4 --- /dev/null +++ b/user/__init__.py @@ -0,0 +1,8 @@ +""" + user module +""" + +from user.model import User +from user.service import UserService + +__all__ = [User, UserService] diff --git a/user/model.py b/user/model.py new file mode 100644 index 0000000..478f27f --- /dev/null +++ b/user/model.py @@ -0,0 +1,21 @@ +""" + user model +""" + +from database.database import Base +from sqlalchemy import Column, Integer, String + + +class User(Base): + __tablename__ = 'users' + + id = Column(Integer, primary_key=True) + email = Column(String(100), unique=True) + password = Column(String(100)) + + def __init__(self, email: str, password: str): + self.email = email + self.password = password + + def __repr__(self): + return f'' diff --git a/services/user/user.py b/user/service.py similarity index 57% rename from services/user/user.py rename to user/service.py index 7c32492..8a487a5 100644 --- a/services/user/user.py +++ b/user/service.py @@ -1,29 +1,15 @@ -from services.bcrypt_wrapper.bcrypt_wrapper import BcryptWrapper -from services.database import Base, db_session -from sqlalchemy import Column, Integer, String +from bcrypt_wrapper.service import BcryptService +from database.database import db_session from sqlalchemy.exc import IntegrityError - -class User(Base): - __tablename__ = 'users' - - id = Column(Integer, primary_key=True) - email = Column(String(100), unique=True) - password = Column(String(100)) - - def __init__(self, email: str, password: str): - self.email = email - self.password = password - - def __repr__(self): - return f'' +from user import User class UserService: @staticmethod def register(email: str, password: str): try: - hashed_password = BcryptWrapper.hash(password) + hashed_password = BcryptService.hash(password) user = User(email, hashed_password) db_session.add(user) db_session.commit() @@ -36,7 +22,7 @@ def register(email: str, password: str): def login(email: str, password: str): user: User = UserService.find_by_email(email) if user: - check_pwd = BcryptWrapper.validate(password, user.password) + check_pwd = BcryptService.validate(password, user.password) return check_pwd return False