feat: error handling, logger configutration, and custom exception for auth
This commit is contained in:
parent
9c3467941b
commit
fab1cea611
|
@ -1 +1,2 @@
|
||||||
from .config import Config
|
from .config import Config
|
||||||
|
from .logger_config import LoggerConfig
|
||||||
|
|
|
@ -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)
|
|
@ -1,15 +1,10 @@
|
||||||
import logging
|
from flask import jsonify, request, current_app
|
||||||
import sys
|
|
||||||
from flask import jsonify, request
|
|
||||||
from pydantic import ValidationError
|
from pydantic import ValidationError
|
||||||
from schemas.basic_response_schema import ResponseSchema
|
from schemas.basic_response_schema import ResponseSchema
|
||||||
from schemas.google_login_schema import GoogleLoginSchema
|
from schemas.google_login_schema import GoogleLoginSchema
|
||||||
from schemas import LoginSchema
|
from schemas import LoginSchema
|
||||||
from services import UserService, AuthService
|
from services import UserService, AuthService
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class AuthController:
|
class AuthController:
|
||||||
def __init__(self, userService: UserService, authService: AuthService):
|
def __init__(self, userService: UserService, authService: AuthService):
|
||||||
|
@ -37,7 +32,7 @@ class AuthController:
|
||||||
# Verifikasi ID Token ke layanan AuthService
|
# Verifikasi ID Token ke layanan AuthService
|
||||||
user_info = self.auth_service.verify_google_id_token(id_token)
|
user_info = self.auth_service.verify_google_id_token(id_token)
|
||||||
if not user_info:
|
if not user_info:
|
||||||
logger.error("Invalid Google ID Token")
|
current_app.logger.error("Invalid Google ID Token")
|
||||||
response = ResponseSchema(
|
response = ResponseSchema(
|
||||||
message="Invalid Google ID Token", data=None, meta=None
|
message="Invalid Google ID Token", data=None, meta=None
|
||||||
)
|
)
|
||||||
|
@ -47,17 +42,19 @@ class AuthController:
|
||||||
response = ResponseSchema(
|
response = ResponseSchema(
|
||||||
message="Login successful",
|
message="Login successful",
|
||||||
data=user_info,
|
data=user_info,
|
||||||
meta=None, # Karena ini single data, tidak ada meta
|
meta=None,
|
||||||
)
|
)
|
||||||
return jsonify(response.model_dump()), 200
|
return jsonify(response.model_dump()), 200
|
||||||
|
|
||||||
except ValidationError as e:
|
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)
|
response = ResponseSchema(message="Invalid input", data=None, meta=None)
|
||||||
return jsonify(response.model_dump()), 400
|
return jsonify(response.model_dump()), 400
|
||||||
|
|
||||||
except Exception as e:
|
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(
|
response = ResponseSchema(
|
||||||
message="Internal server error", data=None, meta=None
|
message="Internal server error", data=None, meta=None
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
from .exception import AuthException
|
|
@ -0,0 +1 @@
|
||||||
|
from .auth_exception import AuthException
|
|
@ -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}"
|
|
@ -1,9 +1,6 @@
|
||||||
import sys
|
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
|
||||||
from blueprints import default_blueprint
|
from blueprints import default_blueprint
|
||||||
from di_container import Container
|
from di_container import Container
|
||||||
from configs import Config
|
from configs import Config, LoggerConfig
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
from blueprints import auth_blueprint
|
from blueprints import auth_blueprint
|
||||||
from database import init_db
|
from database import init_db
|
||||||
|
@ -27,6 +24,9 @@ def createApp() -> Flask:
|
||||||
app.register_blueprint(default_blueprint)
|
app.register_blueprint(default_blueprint)
|
||||||
app.register_blueprint(auth_blueprint, url_prefix="/api")
|
app.register_blueprint(auth_blueprint, url_prefix="/api")
|
||||||
|
|
||||||
|
# Initialize Logging
|
||||||
|
LoggerConfig.init_logger(app)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
from .user_mapper import UserMapper
|
|
@ -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,
|
||||||
|
)
|
|
@ -1,14 +1,10 @@
|
||||||
from datetime import datetime
|
|
||||||
import sys
|
|
||||||
import traceback
|
|
||||||
from schemas import LoginSchema
|
from schemas import LoginSchema
|
||||||
from repositories import UserRepository
|
from repositories import UserRepository
|
||||||
|
from mapper import UserMapper
|
||||||
# from models import ApiResponse
|
|
||||||
from google.oauth2 import id_token
|
from google.oauth2 import id_token
|
||||||
from google.auth.transport import requests
|
from google.auth.transport import requests
|
||||||
from configs import Config
|
from configs import Config
|
||||||
from models import UserEntity
|
from core import AuthException
|
||||||
|
|
||||||
|
|
||||||
class AuthService:
|
class AuthService:
|
||||||
|
@ -16,50 +12,29 @@ class AuthService:
|
||||||
self.user_repository = userRepository
|
self.user_repository = userRepository
|
||||||
|
|
||||||
def verify_google_id_token(self, id_token_str):
|
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:
|
# Verifikasi token Google
|
||||||
print("Invalid token", file=sys.stderr)
|
payload = id_token.verify_oauth2_token(
|
||||||
return None
|
id_token_str, requests.Request(), Config.GOOGLE_CLIENT_ID
|
||||||
|
)
|
||||||
|
|
||||||
google_id = payload.get("sub")
|
if not payload:
|
||||||
email = payload.get("email")
|
return AuthException("Invalid Google ID Token")
|
||||||
|
|
||||||
existing_user = self.user_repository.get_by_google_id(google_id)
|
google_id = payload.get("sub")
|
||||||
if existing_user:
|
email = payload.get("email")
|
||||||
if existing_user.email == email:
|
|
||||||
return existing_user
|
|
||||||
|
|
||||||
new_user = UserEntity(
|
existing_user = self.user_repository.get_by_google_id(google_id)
|
||||||
id=str(google_id),
|
if existing_user:
|
||||||
google_id=google_id,
|
if existing_user.email == email:
|
||||||
email=email,
|
return existing_user
|
||||||
name=payload.get("name"),
|
return AuthException("Email not match")
|
||||||
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,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Simpan user ke database
|
new_user = UserMapper.from_google_payload(google_id, email, payload)
|
||||||
user_id = self.user_repository.insert_user(user_data=new_user)
|
|
||||||
|
|
||||||
# Ambil user yang baru dibuat berdasarkan ID
|
user_id = self.user_repository.insert_user(user_data=new_user)
|
||||||
return self.user_repository.get_user_by_id(user_id=user_id)
|
|
||||||
|
|
||||||
except Exception as e:
|
return self.user_repository.get_user_by_id(user_id=user_id)
|
||||||
traceback.print_exc()
|
|
||||||
print(f"Error verifying Google ID token: {e}", file=sys.stderr)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def login(self, data: LoginSchema):
|
def login(self, data: LoginSchema):
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -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.
|
|
@ -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.
|
Loading…
Reference in New Issue