diff --git a/app/blueprints/__init__.py b/app/blueprints/__init__.py index ffac468..9a7a4d3 100644 --- a/app/blueprints/__init__.py +++ b/app/blueprints/__init__.py @@ -1,5 +1,6 @@ from .default import default_blueprint from .auth import auth_blueprint +from .user import user_blueprint # from .user import user_blueprint diff --git a/app/blueprints/auth.py b/app/blueprints/auth.py index 5834846..ce87c62 100644 --- a/app/blueprints/auth.py +++ b/app/blueprints/auth.py @@ -1,4 +1,3 @@ -import sys from flask import Blueprint from controllers import AuthController from di_container import Container @@ -8,12 +7,6 @@ from dependency_injector.wiring import inject, Provide auth_blueprint = Blueprint("auth", __name__) -@auth_blueprint.route("/register", methods=["POST"]) -@inject -def register(auth_controller: AuthController = Provide[Container.auth_controller]): - return auth_controller.register() - - @auth_blueprint.route("/login", methods=["POST"]) @inject def login(auth_controller: AuthController = Provide[Container.auth_controller]): diff --git a/app/blueprints/user.py b/app/blueprints/user.py index d5b7d5e..182a318 100644 --- a/app/blueprints/user.py +++ b/app/blueprints/user.py @@ -1,12 +1,18 @@ from flask import Blueprint from controllers import UserController -from di_container import container +from di_container import Container +from dependency_injector.wiring import inject, Provide user_blueprint = Blueprint("user", __name__) -user_controller = UserController(container.user_service) - @user_blueprint.route("/users", methods=["GET"]) -def get_users(): +@inject +def get_users(user_controller: UserController = Provide[Container.user_controller]): return user_controller.get_users() + + +@user_blueprint.route("/register", methods=["POST"]) +@inject +def register(user_controller: UserController = Provide[Container.user_controller]): + return user_controller.register() diff --git a/app/controllers/auth_controller.py b/app/controllers/auth_controller.py index 448cedf..f467814 100644 --- a/app/controllers/auth_controller.py +++ b/app/controllers/auth_controller.py @@ -4,7 +4,7 @@ from schemas.basic_response_schema import ResponseSchema from schemas.google_login_schema import GoogleLoginSchema from schemas import LoginSchema from services import UserService, AuthService -from core import AuthException +from exception import AuthException class AuthController: @@ -17,10 +17,10 @@ class AuthController: data = request.get_json() dataSchema = LoginSchema(**data) response = self.auth_service.login(dataSchema) - - if response.success: - return jsonify({}), 200 - return jsonify({}), 400 + return ( + jsonify(ResponseSchema(message="Register success", data=response)), + 200, + ) except ValidationError as e: current_app.logger.error(f"Validation error: {e}") response = ResponseSchema(message="Invalid input", data=None, meta=None) @@ -79,11 +79,5 @@ class AuthController: ) return jsonify(response.model_dump()), 500 - def register(self): - return jsonify({"message": "register"}), 200 - def logout(self): return jsonify({"message": "logout"}), 200 - - def test(self): - return "test" diff --git a/app/controllers/user_controller.py b/app/controllers/user_controller.py index d642a3b..b05cd2c 100644 --- a/app/controllers/user_controller.py +++ b/app/controllers/user_controller.py @@ -1,12 +1,41 @@ # /controllers/user_controller.py -from flask import jsonify +from flask import jsonify, request, current_app from services import UserService +from schemas import RegisterSchema +from pydantic import ValidationError +from schemas import ResponseSchema +from exception import AlreadyExistException class UserController: def __init__(self, userService: UserService): self.user_service = userService - def get_users(self): - users = self.user_service.get_all_users() - return jsonify(users) + def register(self): + try: + request_data = request.get_json() + register_data = RegisterSchema(**request_data) + self.user_service.register_user(register_data) + return jsonify(ResponseSchema(message="Register Success").model_dump()), 200 + + except ValidationError as e: + current_app.logger.error(f"Validation error: {e}") + response = ResponseSchema(message="Invalid input", data=None, meta=None) + return jsonify(response.model_dump()), 400 + + except AlreadyExistException as e: + return ( + jsonify( + ResponseSchema(message=str(e), data=None, meta=None).model_dump() + ), + 409, + ) + + except Exception as e: + current_app.logger.error( + f"Error during Google login: {str(e)}", exc_info=True + ) + response = ResponseSchema( + message="Internal server error", data=None, meta=None + ) + return jsonify(response.model_dump()), 500 diff --git a/app/core/__init__.py b/app/core/__init__.py deleted file mode 100644 index 00ddcd0..0000000 --- a/app/core/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .exception import AuthException diff --git a/app/core/exception/__init__.py b/app/core/exception/__init__.py deleted file mode 100644 index d095db1..0000000 --- a/app/core/exception/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .auth_exception import AuthException diff --git a/app/core/exception/auth_exception.py b/app/core/exception/auth_exception.py deleted file mode 100644 index 3d4b28d..0000000 --- a/app/core/exception/auth_exception.py +++ /dev/null @@ -1,9 +0,0 @@ -class AuthException(Exception): - """Custom exception for authentication-related errors""" - - def __init__(self, message): - super().__init__(message) - self.message = message - - def __str__(self): - return f"AuthException: {self.message}" diff --git a/app/di_container.py b/app/di_container.py index fb7cef5..4a3f7ac 100644 --- a/app/di_container.py +++ b/app/di_container.py @@ -1,4 +1,5 @@ from dependency_injector import containers, providers +from controllers import UserController from repositories.user_repository import UserRepository from services import UserService, AuthService from controllers import AuthController @@ -19,3 +20,4 @@ class Container(containers.DeclarativeContainer): # controllers auth_controller = providers.Factory(AuthController, user_service, auth_service) + user_controller = providers.Factory(UserController, user_service) diff --git a/app/exception/__init__.py b/app/exception/__init__.py new file mode 100644 index 0000000..3b9d50d --- /dev/null +++ b/app/exception/__init__.py @@ -0,0 +1,2 @@ +from .auth_exception import AuthException +from .already_exist_exception import AlreadyExistException diff --git a/app/exception/already_exist_exception.py b/app/exception/already_exist_exception.py new file mode 100644 index 0000000..f3ac645 --- /dev/null +++ b/app/exception/already_exist_exception.py @@ -0,0 +1,6 @@ +class AlreadyExistException(Exception): + def __init__(self, entity: str, message: str = None): + if message is None: + message = f"{entity} already exists" + self.message = message + super().__init__(self.message) diff --git a/app/exception/auth_exception.py b/app/exception/auth_exception.py new file mode 100644 index 0000000..12378f5 --- /dev/null +++ b/app/exception/auth_exception.py @@ -0,0 +1,8 @@ +from .base_exception import BaseExceptionTemplate + + +class AuthException(BaseExceptionTemplate): + """Exception for authentication-related errors""" + + def __init__(self, message: str = "Authentication failed"): + super().__init__(message, status_code=401) diff --git a/app/exception/base_exception.py b/app/exception/base_exception.py new file mode 100644 index 0000000..747b739 --- /dev/null +++ b/app/exception/base_exception.py @@ -0,0 +1,10 @@ +class BaseExceptionTemplate(Exception): + """Base exception template for custom exceptions""" + + def __init__(self, message: str, status_code: int = 400): + self.message = message + self.status_code = status_code + super().__init__(self.message) + + def __str__(self): + return f"{self.__class__.__name__}: {self.message}" diff --git a/app/main.py b/app/main.py index befa064..095c0b9 100644 --- a/app/main.py +++ b/app/main.py @@ -2,7 +2,7 @@ from blueprints import default_blueprint from di_container import Container from configs import Config, LoggerConfig from flask import Flask -from blueprints import auth_blueprint +from blueprints import auth_blueprint, user_blueprint from database import init_db @@ -19,10 +19,12 @@ def createApp() -> Flask: container.mongo.override(mongo) container.wire(modules=["blueprints.auth"]) + container.wire(modules=["blueprints.user"]) # Register Blueprints app.register_blueprint(default_blueprint) app.register_blueprint(auth_blueprint, url_prefix="/api") + app.register_blueprint(user_blueprint, url_prefix="/api") # Initialize Logging LoggerConfig.init_logger(app) diff --git a/app/mapper/user_mapper.py b/app/mapper/user_mapper.py index 6b89c90..d7f16be 100644 --- a/app/mapper/user_mapper.py +++ b/app/mapper/user_mapper.py @@ -1,6 +1,7 @@ from datetime import datetime from typing import Dict, Optional from models import UserEntity +from schemas import RegisterSchema class UserMapper: @@ -22,3 +23,19 @@ class UserMapper: updated_at=datetime.now(), verification_token=None, ) + + @staticmethod + def from_register(data: RegisterSchema) -> UserEntity: + return UserEntity( + email=data.email, + password=data.password, + name=data.name, + birth_date=datetime.strptime(data.birth_date, "%d-%m-%Y").date(), + phone=data.phone, + role="user", + is_active=False, + address=None, + created_at=datetime.now(), + updated_at=datetime.now(), + verification_token=None, + ) diff --git a/app/models/entities/user_entity.py b/app/models/entities/user_entity.py index 9d74109..926ec8d 100644 --- a/app/models/entities/user_entity.py +++ b/app/models/entities/user_entity.py @@ -1,6 +1,6 @@ from typing import Optional from pydantic import BaseModel, EmailStr -from datetime import date, datetime +from datetime import datetime from .base import PyObjectId @@ -10,7 +10,7 @@ class UserEntity(BaseModel): email: EmailStr password: Optional[str] = None name: str - birth_date: Optional[date] = None + birth_date: Optional[datetime] = None pic_url: Optional[str] = None phone: Optional[str] = None locale: str = "en-US" diff --git a/app/schemas/__init__.py b/app/schemas/__init__.py index 77d9edf..215170a 100644 --- a/app/schemas/__init__.py +++ b/app/schemas/__init__.py @@ -1,2 +1,3 @@ from .login_schema import LoginSchema from .basic_response_schema import ResponseSchema, MetaSchema +from .requests import RegisterSchema diff --git a/app/schemas/requests/__init__.py b/app/schemas/requests/__init__.py new file mode 100644 index 0000000..a6389c0 --- /dev/null +++ b/app/schemas/requests/__init__.py @@ -0,0 +1 @@ +from .register_schema import RegisterSchema diff --git a/app/schemas/requests/register_schema.py b/app/schemas/requests/register_schema.py new file mode 100644 index 0000000..57d6006 --- /dev/null +++ b/app/schemas/requests/register_schema.py @@ -0,0 +1,10 @@ +from pydantic import BaseModel +from typing import Optional + + +class RegisterSchema(BaseModel): + email: str + password: str + name: str + birth_date: str + phone: Optional[str] = None diff --git a/app/services/auth_service.py b/app/services/auth_service.py index 1807bcf..22134e9 100644 --- a/app/services/auth_service.py +++ b/app/services/auth_service.py @@ -4,7 +4,7 @@ from mapper import UserMapper from google.oauth2 import id_token from google.auth.transport import requests from configs import Config -from core import AuthException +from exception import AuthException from flask import current_app @@ -13,14 +13,12 @@ class AuthService: self.user_repository = userRepository def verify_google_id_token(self, id_token_str): - - # Verifikasi token Google payload = id_token.verify_oauth2_token( id_token_str, requests.Request(), Config.GOOGLE_CLIENT_ID ) if not payload: - return AuthException("Invalid Google ID Token") + raise AuthException("Invalid Google ID Token") google_id = payload.get("sub") email = payload.get("email") @@ -29,7 +27,7 @@ class AuthService: if existing_user: if existing_user.email == email: return existing_user - return AuthException("Email not match") + raise AuthException("Email not match") new_user = UserMapper.from_google_payload(google_id, email, payload) diff --git a/app/services/user_service.py b/app/services/user_service.py index 8da1b75..bc3f509 100644 --- a/app/services/user_service.py +++ b/app/services/user_service.py @@ -1,4 +1,8 @@ +from flask import current_app from repositories import UserRepository +from schemas import RegisterSchema +from mapper import UserMapper +from exception import AlreadyExistException class UserService: @@ -7,3 +11,11 @@ class UserService: def get_all_users(self): return self.user_repository.get_all_users() + + def register_user(self, user_data: RegisterSchema): + existData = self.user_repository.get_user_by_email(user_data.email) + if existData: + raise AlreadyExistException(entity="Email") + + data = UserMapper.from_register(user_data) + return self.user_repository.insert_user(data)