feat: adding user stat request
This commit is contained in:
parent
7ba31325eb
commit
324173ec84
|
@ -10,7 +10,7 @@ API_URL = "http://127.0.0.1:5000/swagger/docs"
|
|||
swagger_ui_blueprint = get_swaggerui_blueprint(
|
||||
SWAGGER_URL,
|
||||
API_URL,
|
||||
config={"app_name": "Flask API"},
|
||||
config={"app_name": "Quiz Maker API"},
|
||||
)
|
||||
|
||||
swagger_blueprint.register_blueprint(swagger_ui_blueprint)
|
||||
|
|
|
@ -38,3 +38,11 @@ def get_user(
|
|||
user_id, user_controller: UserController = Provide[Container.user_controller]
|
||||
):
|
||||
return user_controller.get_user_by_id(user_id)
|
||||
|
||||
|
||||
@user_blueprint.route("/user/status/<string:user_id>", methods=["GET"])
|
||||
@inject
|
||||
def get_user_stat(
|
||||
user_id, user_controller: UserController = Provide[Container.user_controller]
|
||||
):
|
||||
return user_controller.user_stat(user_id)
|
||||
|
|
|
@ -9,9 +9,14 @@ class LoggerConfig:
|
|||
LOG_DIR = "logs" # Define the log directory
|
||||
|
||||
@staticmethod
|
||||
def init_logger(app):
|
||||
"""Initializes separate log files for INFO, ERROR, and WARNING levels."""
|
||||
def init_logger(app, production_mode=False):
|
||||
"""
|
||||
Initializes separate log files for INFO, ERROR, and WARNING levels.
|
||||
|
||||
Args:
|
||||
app: Flask application instance
|
||||
production_mode (bool): If True, disables console output and only logs to files
|
||||
"""
|
||||
# Ensure the logs directory exists
|
||||
if not os.path.exists(LoggerConfig.LOG_DIR):
|
||||
os.makedirs(LoggerConfig.LOG_DIR)
|
||||
|
@ -20,23 +25,37 @@ class LoggerConfig:
|
|||
for handler in app.logger.handlers[:]:
|
||||
app.logger.removeHandler(handler)
|
||||
|
||||
# Disable propagation to root logger to prevent console output in production
|
||||
if production_mode:
|
||||
app.logger.propagate = False
|
||||
|
||||
# Create separate loggers
|
||||
info_logger = LoggerConfig._setup_logger(
|
||||
info_handler = LoggerConfig._setup_logger(
|
||||
"info_logger", "info.log", logging.INFO, logging.WARNING
|
||||
)
|
||||
error_logger = LoggerConfig._setup_logger(
|
||||
error_handler = LoggerConfig._setup_logger(
|
||||
"error_logger", "error.log", logging.ERROR, logging.CRITICAL
|
||||
)
|
||||
warning_logger = LoggerConfig._setup_logger(
|
||||
warning_handler = LoggerConfig._setup_logger(
|
||||
"warning_logger", "warning.log", logging.WARNING, logging.WARNING
|
||||
)
|
||||
|
||||
# Attach handlers to Flask app logger
|
||||
app.logger.addHandler(info_logger)
|
||||
app.logger.addHandler(error_logger)
|
||||
app.logger.addHandler(warning_logger)
|
||||
|
||||
app.logger.addHandler(info_handler)
|
||||
app.logger.addHandler(error_handler)
|
||||
app.logger.addHandler(warning_handler)
|
||||
app.logger.setLevel(logging.DEBUG) # Set lowest level to capture all logs
|
||||
|
||||
# Add console handler only in development mode
|
||||
if not production_mode:
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setLevel(logging.DEBUG)
|
||||
console_formatter = logging.Formatter(
|
||||
"%(asctime)s - %(levelname)s - %(message)s"
|
||||
)
|
||||
console_handler.setFormatter(console_formatter)
|
||||
app.logger.addHandler(console_handler)
|
||||
|
||||
app.logger.info("Logger has been initialized for Flask application.")
|
||||
|
||||
@staticmethod
|
||||
|
@ -44,11 +63,14 @@ class LoggerConfig:
|
|||
"""Helper method to configure loggers for specific levels."""
|
||||
logger = logging.getLogger(name)
|
||||
logger.setLevel(level)
|
||||
|
||||
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)
|
||||
log_handler.addFilter(lambda record: level <= record.levelno <= max_level)
|
||||
|
||||
logger.addHandler(log_handler)
|
||||
return log_handler
|
||||
|
|
|
@ -114,3 +114,16 @@ class UserController:
|
|||
message="An internal server error occurred. Please try again later.",
|
||||
status_code=500,
|
||||
)
|
||||
|
||||
def user_stat(self, user_id):
|
||||
try:
|
||||
response = self.user_service.get_user_status(user_id)
|
||||
return make_response(message="Success retrive user stat", data=response)
|
||||
except Exception as e:
|
||||
current_app.logger.error(
|
||||
f"Error while changing password: {str(e)}", exc_info=True
|
||||
)
|
||||
return make_response(
|
||||
message="An internal server error occurred. Please try again later.",
|
||||
status_code=500,
|
||||
)
|
||||
|
|
|
@ -66,6 +66,8 @@ class Container(containers.DeclarativeContainer):
|
|||
user_service = providers.Factory(
|
||||
UserService,
|
||||
user_repository,
|
||||
answer_repository,
|
||||
quiz_repository,
|
||||
)
|
||||
|
||||
quiz_service = providers.Factory(
|
||||
|
|
|
@ -25,7 +25,7 @@ from redis import Redis
|
|||
def createApp() -> tuple[Flask, SocketIO]:
|
||||
app = Flask(__name__)
|
||||
app.config.from_object(Config)
|
||||
LoggerConfig.init_logger(app)
|
||||
LoggerConfig.init_logger(app, not Config.DEBUG)
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s"
|
||||
|
|
|
@ -15,3 +15,7 @@ class ResponseSchema(BaseModel, Generic[T]):
|
|||
message: str
|
||||
data: Optional[T] = None
|
||||
meta: Optional[MetaSchema] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
exclude_none = True
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
from datetime import datetime
|
||||
from app.repositories import UserRepository
|
||||
from app.repositories import UserRepository, UserAnswerRepository, QuizRepository
|
||||
from app.schemas import RegisterSchema
|
||||
from app.schemas.requests import ProfileUpdateSchema
|
||||
from app.schemas.response import UserResponseSchema
|
||||
from app.models.entities import UserAnswerEntity
|
||||
from app.mapper import UserMapper
|
||||
from app.exception import AlreadyExistException, DataNotFoundException
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
|
@ -10,8 +10,15 @@ from app.helpers import DatetimeUtil
|
|||
|
||||
|
||||
class UserService:
|
||||
def __init__(self, user_repository: UserRepository):
|
||||
def __init__(
|
||||
self,
|
||||
user_repository: UserRepository,
|
||||
answer_repository: UserAnswerRepository,
|
||||
quiz_repository: QuizRepository,
|
||||
):
|
||||
self.user_repository = user_repository
|
||||
self.answer_repository = answer_repository
|
||||
self.quiz_repository = quiz_repository
|
||||
|
||||
def get_all_users(self):
|
||||
return self.user_repository.get_all_users()
|
||||
|
@ -94,3 +101,24 @@ class UserService:
|
|||
user_dict["updated_at"] = DatetimeUtil.to_string(user_dict["updated_at"])
|
||||
|
||||
return UserResponseSchema(**user_dict)
|
||||
|
||||
def get_user_status(self, user_id):
|
||||
user_answers: list[UserAnswerEntity] = self.answer_repository.get_by_user(
|
||||
user_id
|
||||
)
|
||||
total_quiz = self.quiz_repository.count_by_user_id(user_id)
|
||||
|
||||
if not user_answers:
|
||||
return None
|
||||
|
||||
total_score = sum(answer.total_score for answer in user_answers)
|
||||
|
||||
total_questions = len(user_answers)
|
||||
|
||||
percentage = total_score / total_questions
|
||||
|
||||
return {
|
||||
"avg_score": round(percentage, 2),
|
||||
"total_solve": total_questions,
|
||||
"total_quiz": total_quiz,
|
||||
}
|
||||
|
|
|
@ -77,14 +77,59 @@ paths:
|
|||
"200":
|
||||
$ref: "#/components/responses/UserData"
|
||||
|
||||
put:
|
||||
summary: Update User
|
||||
/user/update:
|
||||
post:
|
||||
summary: Update user profile
|
||||
tags: [User]
|
||||
requestBody:
|
||||
$ref: "#/components/requestBodies/UpdateUserRequest"
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
description: Unique identifier of the user
|
||||
example: "123abc"
|
||||
name:
|
||||
type: string
|
||||
nullable: true
|
||||
example: "John Doe"
|
||||
birth_date:
|
||||
type: string
|
||||
format: date
|
||||
nullable: true
|
||||
example: "1990-01-01"
|
||||
locale:
|
||||
type: string
|
||||
nullable: true
|
||||
example: "en-US"
|
||||
phone:
|
||||
type: string
|
||||
nullable: true
|
||||
example: "+628123456789"
|
||||
required:
|
||||
- id
|
||||
responses:
|
||||
"200":
|
||||
$ref: "#/components/responses/UserUpdated"
|
||||
description: Profile updated successfully
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
example: User profile updated successfully.
|
||||
data:
|
||||
type: object
|
||||
nullable: true
|
||||
example: null
|
||||
meta:
|
||||
type: object
|
||||
nullable: true
|
||||
example: null
|
||||
|
||||
/quiz:
|
||||
post:
|
||||
|
|
Loading…
Reference in New Issue