validations and dockerfiles are updated
This commit is contained in:
@@ -1,15 +1,18 @@
|
||||
import arrow
|
||||
import calendar
|
||||
from api_configs.configs import Config
|
||||
from datetime import timedelta
|
||||
|
||||
|
||||
class DateTimeLocal:
|
||||
|
||||
__SYSTEM__: str = "GMT+0"
|
||||
|
||||
def __init__(self, timezone: str = "GMT+3", is_client: bool = True):
|
||||
self.timezone = self.__SYSTEM__
|
||||
def __init__(self, timezone: str = None, is_client: bool = True):
|
||||
if timezone and timezone not in Config.SUPPORTED_TIMEZONES:
|
||||
raise ValueError(f"Unsupported timezone: {timezone}. Must be one of {Config.SUPPORTED_TIMEZONES}")
|
||||
|
||||
self.timezone = Config.SYSTEM_TIMEZONE
|
||||
if is_client:
|
||||
self.timezone = timezone.replace("-", "+")
|
||||
self.timezone = (timezone or Config.DEFAULT_TIMEZONE).replace("-", "+")
|
||||
|
||||
def find_last_day_of_month(self, date_value):
|
||||
today = self.get(date_value).date()
|
||||
@@ -44,6 +47,68 @@ class DateTimeLocal:
|
||||
def string_date_only(self, date):
|
||||
return self.get(date).format("YYYY-MM-DD")
|
||||
|
||||
def to_timestamp(self, date):
|
||||
"""Convert datetime to UTC timestamp"""
|
||||
return self.get(date).timestamp()
|
||||
|
||||
def from_timestamp(self, timestamp):
|
||||
"""Convert timestamp to timezone-aware datetime"""
|
||||
return arrow.get(timestamp).to(str(self.timezone))
|
||||
|
||||
def is_timezone_aware(self, date):
|
||||
"""Check if a date is timezone-aware"""
|
||||
return self.get(date).tzinfo is not None
|
||||
|
||||
def standardize_timezone(self, date):
|
||||
"""Ensure date is in the correct timezone"""
|
||||
if not self.is_timezone_aware(date):
|
||||
return self.get(date).to(str(self.timezone))
|
||||
return self.get(date)
|
||||
|
||||
def get_expiry_time(self, **kwargs):
|
||||
"""Get future time for cache expiry
|
||||
Example: get_expiry_time(hours=1, minutes=30)
|
||||
"""
|
||||
return self.now().shift(**kwargs)
|
||||
|
||||
def is_expired(self, timestamp):
|
||||
"""Check if a timestamp is expired"""
|
||||
if not timestamp:
|
||||
return True
|
||||
return self.from_timestamp(timestamp) < self.now()
|
||||
|
||||
def get_cache_key(self, base_key, *args):
|
||||
"""Generate a cache key with timezone info
|
||||
Example: get_cache_key('user_profile', user_id, 'details')
|
||||
"""
|
||||
components = [str(base_key)]
|
||||
components.extend(str(arg) for arg in args)
|
||||
components.append(f"tz_{self.timezone}")
|
||||
return ':'.join(components)
|
||||
|
||||
def format_for_db(self, date):
|
||||
"""Format date for database storage"""
|
||||
return self.get(date).format('YYYY-MM-DD HH:mm:ss.SSSZZ')
|
||||
|
||||
def parse_from_db(self, date_str):
|
||||
"""Parse date from database format"""
|
||||
if not date_str:
|
||||
return None
|
||||
return self.get(date_str)
|
||||
|
||||
def get_day_boundaries(self, date=None):
|
||||
"""Get start and end of day in current timezone"""
|
||||
dt = self.get(date) if date else self.now()
|
||||
start = dt.floor('day')
|
||||
end = dt.ceil('day')
|
||||
return start, end
|
||||
|
||||
def get_month_boundaries(self, date=None):
|
||||
"""Get start and end of month in current timezone"""
|
||||
dt = self.get(date) if date else self.now()
|
||||
start = dt.floor('month')
|
||||
end = dt.ceil('month')
|
||||
return start, end
|
||||
|
||||
client_arrow = DateTimeLocal(is_client=True)
|
||||
system_arrow = DateTimeLocal(is_client=False)
|
||||
|
||||
109
api_library/logger.py
Normal file
109
api_library/logger.py
Normal file
@@ -0,0 +1,109 @@
|
||||
import logging
|
||||
import os
|
||||
from typing import Optional, Dict, Any
|
||||
from fastapi.requests import Request
|
||||
from api_library.date_time_actions.date_functions import system_arrow
|
||||
|
||||
class UserActivityLogger:
|
||||
def __init__(self):
|
||||
self.logger = logging.getLogger("user_activity")
|
||||
self.logger.setLevel(logging.INFO)
|
||||
|
||||
# Add handlers if not already added
|
||||
if not self.logger.handlers:
|
||||
log_path = "/service_app/logs/user_activity.log"
|
||||
os.makedirs(os.path.dirname(log_path), exist_ok=True)
|
||||
handler = logging.FileHandler(log_path)
|
||||
formatter = logging.Formatter(
|
||||
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
handler.setFormatter(formatter)
|
||||
self.logger.addHandler(handler)
|
||||
|
||||
def _get_request_metadata(self, request: Request) -> Dict[str, Any]:
|
||||
"""Extract common metadata from request"""
|
||||
return {
|
||||
"agent": request.headers.get("User-Agent"),
|
||||
"ip": getattr(request, "remote_addr", None) or request.headers.get("X-Forwarded-For"),
|
||||
"platform": request.headers.get("Origin"),
|
||||
"timestamp": str(system_arrow.now())
|
||||
}
|
||||
|
||||
def log_login_attempt(
|
||||
self,
|
||||
request: Request,
|
||||
user_id: int,
|
||||
domain: str,
|
||||
access_key: str,
|
||||
success: bool,
|
||||
error: Optional[str] = None
|
||||
):
|
||||
"""Log login attempts"""
|
||||
metadata = self._get_request_metadata(request)
|
||||
log_data = {
|
||||
"event": "login_attempt",
|
||||
"user_id": user_id,
|
||||
"domain": domain,
|
||||
"access_key": access_key,
|
||||
"success": success,
|
||||
"error": error,
|
||||
**metadata
|
||||
}
|
||||
|
||||
if success:
|
||||
self.logger.info("Login successful", extra=log_data)
|
||||
else:
|
||||
self.logger.warning("Login failed", extra=log_data)
|
||||
|
||||
def log_password_change(
|
||||
self,
|
||||
request: Request,
|
||||
user_id: int,
|
||||
change_type: str,
|
||||
success: bool,
|
||||
error: Optional[str] = None
|
||||
):
|
||||
"""Log password changes"""
|
||||
metadata = self._get_request_metadata(request)
|
||||
log_data = {
|
||||
"event": "password_change",
|
||||
"user_id": user_id,
|
||||
"change_type": change_type,
|
||||
"success": success,
|
||||
"error": error,
|
||||
**metadata
|
||||
}
|
||||
|
||||
if success:
|
||||
self.logger.info("Password change successful", extra=log_data)
|
||||
else:
|
||||
self.logger.warning("Password change failed", extra=log_data)
|
||||
|
||||
def log_session_activity(
|
||||
self,
|
||||
request: Request,
|
||||
user_id: int,
|
||||
activity_type: str,
|
||||
domain: Optional[str] = None,
|
||||
success: bool = True,
|
||||
error: Optional[str] = None
|
||||
):
|
||||
"""Log session activities (logout, disconnect, etc)"""
|
||||
metadata = self._get_request_metadata(request)
|
||||
log_data = {
|
||||
"event": "session_activity",
|
||||
"activity_type": activity_type,
|
||||
"user_id": user_id,
|
||||
"domain": domain,
|
||||
"success": success,
|
||||
"error": error,
|
||||
**metadata
|
||||
}
|
||||
|
||||
if success:
|
||||
self.logger.info(f"{activity_type} successful", extra=log_data)
|
||||
else:
|
||||
self.logger.warning(f"{activity_type} failed", extra=log_data)
|
||||
|
||||
# Global logger instance
|
||||
user_logger = UserActivityLogger()
|
||||
38
api_library/response_handlers.py
Normal file
38
api_library/response_handlers.py
Normal file
@@ -0,0 +1,38 @@
|
||||
from typing import Any, Optional
|
||||
from fastapi import status
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
class ResponseHandler:
|
||||
@staticmethod
|
||||
def success(message: str, data: Optional[Any] = None, status_code: int = status.HTTP_200_OK) -> JSONResponse:
|
||||
"""Create a success response"""
|
||||
return JSONResponse(
|
||||
content={
|
||||
"completed": True,
|
||||
"message": message,
|
||||
"data": data or {},
|
||||
},
|
||||
status_code=status_code,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def error(message: str, data: Optional[Any] = None, status_code: int = status.HTTP_400_BAD_REQUEST) -> JSONResponse:
|
||||
"""Create an error response"""
|
||||
return JSONResponse(
|
||||
content={
|
||||
"completed": False,
|
||||
"message": message,
|
||||
"data": data or {},
|
||||
},
|
||||
status_code=status_code,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def unauthorized(message: str = "Unauthorized access") -> JSONResponse:
|
||||
"""Create an unauthorized response"""
|
||||
return ResponseHandler.error(message, status_code=status.HTTP_401_UNAUTHORIZED)
|
||||
|
||||
@staticmethod
|
||||
def not_found(message: str = "Resource not found") -> JSONResponse:
|
||||
"""Create a not found response"""
|
||||
return ResponseHandler.error(message, status_code=status.HTTP_404_NOT_FOUND)
|
||||
Reference in New Issue
Block a user