From fab1cea6111e945ddef6e811cf863d15d4f5670c Mon Sep 17 00:00:00 2001 From: akhdanre Date: Wed, 19 Mar 2025 11:50:02 +0700 Subject: [PATCH] feat: error handling, logger configutration, and custom exception for auth --- app/configs/__init__.py | 1 + app/configs/logger_config.py | 35 ++++++++++++++++ app/controllers/auth_controller.py | 17 ++++---- app/core/__init__.py | 1 + app/core/exception/__init__.py | 1 + app/core/exception/auth_exception.py | 9 ++++ app/main.py | 8 ++-- app/mapper/__init__.py | 1 + app/mapper/user_mapper.py | 25 ++++++++++++ app/services/auth_service.py | 61 ++++++++-------------------- logs/app.log | 2 + logs/error.log | 0 logs/info.log | 3 ++ logs/warning.log | 0 14 files changed, 107 insertions(+), 57 deletions(-) create mode 100644 app/configs/logger_config.py create mode 100644 app/core/__init__.py create mode 100644 app/core/exception/__init__.py create mode 100644 app/core/exception/auth_exception.py create mode 100644 app/mapper/__init__.py create mode 100644 app/mapper/user_mapper.py create mode 100644 logs/app.log create mode 100644 logs/error.log create mode 100644 logs/info.log create mode 100644 logs/warning.log diff --git a/app/configs/__init__.py b/app/configs/__init__.py index cca5d9b..260ca6f 100644 --- a/app/configs/__init__.py +++ b/app/configs/__init__.py @@ -1 +1,2 @@ from .config import Config +from .logger_config import LoggerConfig diff --git a/app/configs/logger_config.py b/app/configs/logger_config.py new file mode 100644 index 0000000..60bbd33 --- /dev/null +++ b/app/configs/logger_config.py @@ -0,0 +1,35 @@ +import logging +import os +from logging.handlers import RotatingFileHandler + + +class LoggerConfig: + """A class to configure logging for the Flask application.""" + + LOG_DIR = "logs" # Define the log directory + + @staticmethod + def init_logger(app): + """Initializes separate log files for INFO, ERROR, and WARNING levels.""" + + # Ensure the logs directory exists + if not os.path.exists(LoggerConfig.LOG_DIR): + os.makedirs(LoggerConfig.LOG_DIR) + + # Separate loggers for different levels + LoggerConfig._setup_logger(app.logger, "info.log", logging.INFO) + LoggerConfig._setup_logger(app.logger, "error.log", logging.ERROR) + LoggerConfig._setup_logger(app.logger, "warning.log", logging.WARNING) + + app.logger.info("Logger has been initialized for Flask application.") + + @staticmethod + def _setup_logger(logger, filename, level): + """Helper method to configure loggers for specific levels.""" + log_file_path = os.path.join(LoggerConfig.LOG_DIR, filename) + log_handler = RotatingFileHandler(log_file_path, maxBytes=100000, backupCount=3) + log_handler.setLevel(level) + log_formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") + log_handler.setFormatter(log_formatter) + + logger.addHandler(log_handler) diff --git a/app/controllers/auth_controller.py b/app/controllers/auth_controller.py index 8b78423..4f7d927 100644 --- a/app/controllers/auth_controller.py +++ b/app/controllers/auth_controller.py @@ -1,15 +1,10 @@ -import logging -import sys -from flask import jsonify, request +from flask import jsonify, request, current_app from pydantic import ValidationError from schemas.basic_response_schema import ResponseSchema from schemas.google_login_schema import GoogleLoginSchema from schemas import LoginSchema from services import UserService, AuthService -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) - class AuthController: def __init__(self, userService: UserService, authService: AuthService): @@ -37,7 +32,7 @@ class AuthController: # Verifikasi ID Token ke layanan AuthService user_info = self.auth_service.verify_google_id_token(id_token) if not user_info: - logger.error("Invalid Google ID Token") + current_app.logger.error("Invalid Google ID Token") response = ResponseSchema( message="Invalid Google ID Token", data=None, meta=None ) @@ -47,17 +42,19 @@ class AuthController: response = ResponseSchema( message="Login successful", data=user_info, - meta=None, # Karena ini single data, tidak ada meta + meta=None, ) return jsonify(response.model_dump()), 200 except ValidationError as e: - logger.error(f"Validation error: {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 Exception as e: - logger.error(f"Error during Google login: {str(e)}", exc_info=True) + current_app.logger.error( + f"Error during Google login: {str(e)}", exc_info=True + ) response = ResponseSchema( message="Internal server error", data=None, meta=None ) diff --git a/app/core/__init__.py b/app/core/__init__.py new file mode 100644 index 0000000..00ddcd0 --- /dev/null +++ b/app/core/__init__.py @@ -0,0 +1 @@ +from .exception import AuthException diff --git a/app/core/exception/__init__.py b/app/core/exception/__init__.py new file mode 100644 index 0000000..d095db1 --- /dev/null +++ b/app/core/exception/__init__.py @@ -0,0 +1 @@ +from .auth_exception import AuthException diff --git a/app/core/exception/auth_exception.py b/app/core/exception/auth_exception.py new file mode 100644 index 0000000..3d4b28d --- /dev/null +++ b/app/core/exception/auth_exception.py @@ -0,0 +1,9 @@ +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/main.py b/app/main.py index 73252af..befa064 100644 --- a/app/main.py +++ b/app/main.py @@ -1,9 +1,6 @@ -import sys - -from dotenv import load_dotenv from blueprints import default_blueprint from di_container import Container -from configs import Config +from configs import Config, LoggerConfig from flask import Flask from blueprints import auth_blueprint from database import init_db @@ -27,6 +24,9 @@ def createApp() -> Flask: app.register_blueprint(default_blueprint) app.register_blueprint(auth_blueprint, url_prefix="/api") + # Initialize Logging + LoggerConfig.init_logger(app) + return app diff --git a/app/mapper/__init__.py b/app/mapper/__init__.py new file mode 100644 index 0000000..eeb7873 --- /dev/null +++ b/app/mapper/__init__.py @@ -0,0 +1 @@ +from .user_mapper import UserMapper diff --git a/app/mapper/user_mapper.py b/app/mapper/user_mapper.py new file mode 100644 index 0000000..999a4a2 --- /dev/null +++ b/app/mapper/user_mapper.py @@ -0,0 +1,25 @@ +from datetime import datetime +from typing import Dict, Optional +from models import UserEntity + + +class UserMapper: + @staticmethod + def from_google_payload( + google_id: str, email: str, payload: Dict[str, Optional[str]] + ) -> UserEntity: + return UserEntity( + id=str(google_id), + google_id=google_id, + email=email, + name=payload.get("name"), + pic_url=payload.get("picture"), + birth_date=None, + phone=None, + role="user", + is_active=True, + address=None, + created_at=datetime.now(), + updated_at=datetime.now(), + verification_token=None, + ) diff --git a/app/services/auth_service.py b/app/services/auth_service.py index f335adf..f7fac39 100644 --- a/app/services/auth_service.py +++ b/app/services/auth_service.py @@ -1,14 +1,10 @@ -from datetime import datetime -import sys -import traceback from schemas import LoginSchema from repositories import UserRepository - -# from models import ApiResponse +from mapper import UserMapper from google.oauth2 import id_token from google.auth.transport import requests from configs import Config -from models import UserEntity +from core import AuthException class AuthService: @@ -16,50 +12,29 @@ class AuthService: self.user_repository = userRepository def verify_google_id_token(self, id_token_str): - try: - # Verifikasi token Google - payload = id_token.verify_oauth2_token( - id_token_str, requests.Request(), Config.GOOGLE_CLIENT_ID - ) - if not payload: - print("Invalid token", file=sys.stderr) - return None + # Verifikasi token Google + payload = id_token.verify_oauth2_token( + id_token_str, requests.Request(), Config.GOOGLE_CLIENT_ID + ) - google_id = payload.get("sub") - email = payload.get("email") + if not payload: + return AuthException("Invalid Google ID Token") - existing_user = self.user_repository.get_by_google_id(google_id) - if existing_user: - if existing_user.email == email: - return existing_user + google_id = payload.get("sub") + email = payload.get("email") - new_user = UserEntity( - id=str(google_id), - google_id=google_id, - email=email, - name=payload.get("name"), - pic_url=payload.get("picture"), - birth_date=None, - phone=None, - role="user", - is_active=True, - address=None, - created_at=datetime.now(), - updated_at=datetime.now(), - verification_token=None, - ) + existing_user = self.user_repository.get_by_google_id(google_id) + if existing_user: + if existing_user.email == email: + return existing_user + return AuthException("Email not match") - # Simpan user ke database - user_id = self.user_repository.insert_user(user_data=new_user) + new_user = UserMapper.from_google_payload(google_id, email, payload) - # Ambil user yang baru dibuat berdasarkan ID - return self.user_repository.get_user_by_id(user_id=user_id) + user_id = self.user_repository.insert_user(user_data=new_user) - except Exception as e: - traceback.print_exc() - print(f"Error verifying Google ID token: {e}", file=sys.stderr) - return None + return self.user_repository.get_user_by_id(user_id=user_id) def login(self, data: LoginSchema): try: diff --git a/logs/app.log b/logs/app.log new file mode 100644 index 0000000..5f7695d --- /dev/null +++ b/logs/app.log @@ -0,0 +1,2 @@ +2025-03-19 11:45:54,493 - INFO - Logger has been initialized for Flask application. +2025-03-19 11:46:06,381 - INFO - Logger has been initialized for Flask application. diff --git a/logs/error.log b/logs/error.log new file mode 100644 index 0000000..e69de29 diff --git a/logs/info.log b/logs/info.log new file mode 100644 index 0000000..0b927e5 --- /dev/null +++ b/logs/info.log @@ -0,0 +1,3 @@ +2025-03-19 11:48:56,950 - INFO - Logger has been initialized for Flask application. +2025-03-19 11:49:01,795 - INFO - Logger has been initialized for Flask application. +2025-03-19 11:49:06,664 - INFO - Logger has been initialized for Flask application. diff --git a/logs/warning.log b/logs/warning.log new file mode 100644 index 0000000..e69de29