403 lines
14 KiB
Python
403 lines
14 KiB
Python
import json
|
|
import typing
|
|
|
|
from api_services.redis.conn import redis_cli
|
|
from api_library.date_time_actions.date_functions import system_arrow, DateTimeLocal
|
|
from api_objects import OccupantTokenObject, EmployeeTokenObject
|
|
|
|
|
|
class RedisActions:
|
|
|
|
@classmethod
|
|
def set_json(cls, name, value):
|
|
try:
|
|
search_name = str(name) if isinstance(name, str) else name.decode()
|
|
redis_cli.set(name=search_name, value=json.dumps(value))
|
|
return RedisResponse(
|
|
status=True,
|
|
message="Value is set successfully.",
|
|
data=value,
|
|
)
|
|
except Exception as e:
|
|
return RedisResponse(
|
|
status=False,
|
|
message="Value is not set successfully.",
|
|
error=str(e),
|
|
)
|
|
|
|
@classmethod
|
|
def get_json(cls, name):
|
|
try:
|
|
search_name = str(name) if isinstance(name, str) else name.decode()
|
|
json_get = redis_cli.get(search_name)
|
|
if not json_get:
|
|
return RedisResponse(
|
|
status=False,
|
|
message="Value is not get successfully.",
|
|
error="Value is not found in the redis.",
|
|
)
|
|
return RedisResponse(
|
|
status=True,
|
|
message="Value is get successfully.",
|
|
data=json.loads(json_get),
|
|
)
|
|
except Exception as e:
|
|
return RedisResponse(
|
|
status=False,
|
|
message="Value is not get successfully.",
|
|
error=str(e),
|
|
)
|
|
|
|
@classmethod
|
|
def get_with_regex(cls, value_regex):
|
|
try:
|
|
already_tokens = redis_cli.scan_iter(match=str(value_regex))
|
|
for already_token in already_tokens:
|
|
return RedisResponse(
|
|
status=True,
|
|
message="Single value is retrieved successfully.",
|
|
data=json.loads(redis_cli.get(already_token)),
|
|
)
|
|
except Exception as e:
|
|
return RedisResponse(
|
|
status=False,
|
|
message="Values are not listed successfully.",
|
|
error=str(e),
|
|
)
|
|
|
|
@classmethod
|
|
def list_all(cls, value_regex):
|
|
try:
|
|
already_tokens = redis_cli.scan_iter(match=str(value_regex))
|
|
already_tokens_list = {}
|
|
for already_token in already_tokens:
|
|
already_tokens_list[already_token.decode()] = json.loads(
|
|
redis_cli.get(already_token)
|
|
)
|
|
return RedisResponse(
|
|
status=True,
|
|
message="Values are listed successfully.",
|
|
data=already_tokens_list,
|
|
)
|
|
except Exception as e:
|
|
return RedisResponse(
|
|
status=False,
|
|
message="Values are not listed successfully.",
|
|
error=str(e),
|
|
)
|
|
|
|
@classmethod
|
|
def set_replace_all(cls, value, value_regex):
|
|
try:
|
|
already_tokens = redis_cli.scan_iter(match=str(value_regex))
|
|
for already_token in already_tokens:
|
|
redis_cli.set(name=already_token, value=json.dumps(value))
|
|
return RedisResponse(
|
|
status=True,
|
|
message="Value is set successfully.",
|
|
data=value,
|
|
)
|
|
except Exception as e:
|
|
return RedisResponse(
|
|
status=False,
|
|
message="Value is not set successfully.",
|
|
error=str(e),
|
|
)
|
|
|
|
@classmethod
|
|
def delete(cls, name):
|
|
try:
|
|
search_name = str(name) if isinstance(name, str) else name.decode()
|
|
json_delete = redis_cli.delete(search_name)
|
|
if not json_delete:
|
|
return RedisResponse(
|
|
status=False,
|
|
message="Value is not deleted successfully.",
|
|
error="Value is not found in the redis.",
|
|
)
|
|
return RedisResponse(
|
|
status=True,
|
|
message="Value is deleted successfully.",
|
|
)
|
|
except Exception as e:
|
|
return RedisResponse(
|
|
status=False,
|
|
message="Value is not deleted successfully.",
|
|
error=str(e),
|
|
)
|
|
|
|
@classmethod
|
|
def delete_all(cls, value_regex):
|
|
try:
|
|
already_tokens = redis_cli.scan_iter(match=str(value_regex))
|
|
for already_token in already_tokens:
|
|
redis_cli.delete(already_token)
|
|
return RedisResponse(
|
|
status=True,
|
|
message="Value is deleted successfully.",
|
|
)
|
|
except Exception as e:
|
|
return RedisResponse(
|
|
status=False,
|
|
message="Value is not deleted successfully.",
|
|
error=str(e),
|
|
)
|
|
|
|
@classmethod
|
|
def update(cls, name, data):
|
|
try:
|
|
json_update = cls.get_json(name=name)
|
|
if json_update.status:
|
|
value_dict = json_update.data
|
|
for key, value in data.items():
|
|
value_dict[key] = value
|
|
redis_cli.set(name=name, value=json.dumps(value_dict))
|
|
return RedisResponse(
|
|
status=True,
|
|
message="Value is updated successfully.",
|
|
data=value_dict,
|
|
)
|
|
except Exception as e:
|
|
return RedisResponse(
|
|
status=False,
|
|
message="Value is not updated successfully.",
|
|
error=str(e),
|
|
)
|
|
|
|
@classmethod
|
|
def update_all(cls, value_regex, data):
|
|
try:
|
|
already_tokens = redis_cli.scan_iter(match=str(value_regex))
|
|
for already_token in already_tokens:
|
|
cls.update(name=already_token, data=data)
|
|
return RedisResponse(
|
|
status=True,
|
|
message="Values are updated successfully.",
|
|
data=data,
|
|
)
|
|
except Exception as e:
|
|
return RedisResponse(
|
|
status=False,
|
|
message="Value is not updated successfully.",
|
|
error=str(e),
|
|
)
|
|
|
|
@classmethod
|
|
def set_json_with_expiry(cls, name, value, **expiry_kwargs):
|
|
"""Set JSON value with expiry time
|
|
Example: set_json_with_expiry('key', value, hours=1, minutes=30)
|
|
"""
|
|
try:
|
|
search_name = str(name) if isinstance(name, str) else name.decode()
|
|
expiry_time = system_arrow.get_expiry_time(**expiry_kwargs)
|
|
seconds_until_expiry = int(
|
|
expiry_time.timestamp() - system_arrow.now().timestamp()
|
|
)
|
|
|
|
redis_cli.setex(
|
|
name=search_name,
|
|
time=seconds_until_expiry,
|
|
value=json.dumps(
|
|
{"value": value, "expires_at": expiry_time.timestamp()}
|
|
),
|
|
)
|
|
|
|
return RedisResponse(
|
|
status=True,
|
|
message="Value is set successfully with expiry.",
|
|
data={"value": value, "expires_at": expiry_time.timestamp()},
|
|
)
|
|
except Exception as e:
|
|
return RedisResponse(
|
|
status=False,
|
|
message="Value is not set successfully.",
|
|
error=str(e),
|
|
)
|
|
|
|
@classmethod
|
|
def get_json_if_valid(cls, name):
|
|
"""Get JSON value if not expired"""
|
|
try:
|
|
search_name = str(name) if isinstance(name, str) else name.decode()
|
|
result = redis_cli.get(name=search_name)
|
|
|
|
if not result:
|
|
return RedisResponse(
|
|
status=False,
|
|
message="Key not found.",
|
|
)
|
|
|
|
data = json.loads(result)
|
|
if system_arrow.is_expired(data.get("expires_at")):
|
|
redis_cli.delete(search_name)
|
|
return RedisResponse(
|
|
status=False,
|
|
message="Cache expired.",
|
|
)
|
|
|
|
return RedisResponse(
|
|
status=True,
|
|
message="Value retrieved successfully.",
|
|
data=data["value"],
|
|
)
|
|
except Exception as e:
|
|
return RedisResponse(
|
|
status=False,
|
|
message="Error retrieving value.",
|
|
error=str(e),
|
|
)
|
|
|
|
@classmethod
|
|
def cache_with_timezone(cls, name, value, timezone=None, **expiry_kwargs):
|
|
"""Cache value with timezone-aware key"""
|
|
try:
|
|
dt = DateTimeLocal(timezone=timezone) if timezone else system_arrow
|
|
cache_key = dt.get_cache_key(name)
|
|
return cls.set_json_with_expiry(cache_key, value, **expiry_kwargs)
|
|
except Exception as e:
|
|
return RedisResponse(
|
|
status=False,
|
|
message="Error caching with timezone.",
|
|
error=str(e),
|
|
)
|
|
|
|
@classmethod
|
|
def save_object_to_redis(cls, access_token, model_object, expiry_minutes: int = 30):
|
|
"""Save object to Redis with expiry time
|
|
Args:
|
|
access_token: The access token to use as key prefix
|
|
model_object: The model object to save (must have user_uu_id attribute and model_dump_json method)
|
|
expiry_minutes: Minutes until the token expires (default: 30)
|
|
Returns:
|
|
str: The access token if successful
|
|
Raises:
|
|
Exception: If saving fails
|
|
"""
|
|
try:
|
|
key = f"{access_token}:{model_object.user_uu_id}"
|
|
expiry_time = system_arrow.get_expiry_time(minutes=expiry_minutes)
|
|
seconds_until_expiry = max(
|
|
1, int(expiry_time.timestamp() - system_arrow.now().timestamp())
|
|
)
|
|
|
|
# Add expiry time to the model data
|
|
model_data = json.loads(model_object.model_dump_json())
|
|
model_data["expires_at"] = expiry_time.timestamp()
|
|
|
|
if redis_cli.setex(
|
|
name=key, time=seconds_until_expiry, value=json.dumps(model_data)
|
|
):
|
|
return access_token
|
|
|
|
except Exception as e:
|
|
raise Exception(f"Failed to save object to Redis. Error: {str(e)}")
|
|
|
|
raise Exception("Failed to save token to Redis")
|
|
|
|
@classmethod
|
|
def get_object_via_access_key(cls, request):
|
|
"""Get object from Redis using access key from request headers
|
|
Args:
|
|
request: The request object containing headers
|
|
Returns:
|
|
Union[EmployeeTokenObject, OccupantTokenObject]: The token object
|
|
Raises:
|
|
Exception: If retrieval fails or token is invalid
|
|
"""
|
|
from api_configs.configs import Auth
|
|
from api_library.date_time_actions.date_functions import system_arrow
|
|
|
|
if not hasattr(request, "headers"):
|
|
raise Exception("Headers not found in request")
|
|
|
|
access_token = request.headers.get(Auth.ACCESS_TOKEN_TAG)
|
|
if not access_token:
|
|
raise Exception("Unauthorized user, please login")
|
|
|
|
# Scan for matching tokens
|
|
token_pattern = f"{access_token}:*"
|
|
matching_tokens = list(redis_cli.scan_iter(match=token_pattern))
|
|
|
|
if not matching_tokens:
|
|
raise Exception("Invalid credentials. Please login again")
|
|
|
|
try:
|
|
# Check if token has expired in Redis
|
|
token_key = matching_tokens[0]
|
|
ttl = redis_cli.ttl(token_key)
|
|
if ttl <= 0:
|
|
redis_cli.delete(token_key)
|
|
raise Exception("Token expired. Please login again")
|
|
|
|
# Get the token data
|
|
token_data = json.loads(redis_cli.get(token_key) or "{}")
|
|
|
|
# Return appropriate token object based on user type
|
|
if token_data.get("user_type") == 1: # Employee
|
|
if not token_data.get("selected_company"):
|
|
token_data["selected_company"] = None
|
|
return EmployeeTokenObject(**token_data)
|
|
|
|
elif token_data.get("user_type") == 2: # Occupant
|
|
if not token_data.get("selected_occupant"):
|
|
token_data["selected_occupant"] = None
|
|
return OccupantTokenObject(**token_data)
|
|
|
|
raise Exception("Invalid user type in token")
|
|
|
|
except Exception as e:
|
|
raise Exception(f"Failed to retrieve token: {str(e)}")
|
|
|
|
@classmethod
|
|
def get_object_via_user_uu_id(cls, user_id: str) -> typing.Union[dict, None]:
|
|
"""Get all objects for a user by UUID
|
|
Args:
|
|
user_id: The user UUID to search for
|
|
Returns:
|
|
dict: Dictionary of token keys and their corresponding objects
|
|
"""
|
|
token_pattern = f"*:{str(user_id)}"
|
|
matching_tokens = redis_cli.scan_iter(match=token_pattern)
|
|
|
|
tokens_dict = {}
|
|
for token_key in matching_tokens:
|
|
token_data = json.loads(redis_cli.get(token_key) or "{}")
|
|
|
|
# Skip expired tokens and clean them up
|
|
if system_arrow.is_expired(token_data.get("expires_at")):
|
|
redis_cli.delete(token_key)
|
|
continue
|
|
|
|
tokens_dict[token_key.decode()] = token_data
|
|
|
|
return tokens_dict
|
|
|
|
|
|
class RedisResponse:
|
|
def __init__(
|
|
self,
|
|
status: bool,
|
|
message: str,
|
|
data: typing.Union[dict | list] = None,
|
|
error: str = None,
|
|
):
|
|
self.status = status
|
|
self.message = message
|
|
self.data = data
|
|
if isinstance(data, dict):
|
|
self.data_type = "dict"
|
|
elif isinstance(data, list):
|
|
self.data_type = "list"
|
|
elif data is None:
|
|
self.data_type = None
|
|
self.error = error
|
|
|
|
def as_dict(self):
|
|
return {
|
|
"status": self.status,
|
|
"message": self.message,
|
|
"data": self.data,
|
|
"data_type": self.data_type,
|
|
"error": self.error,
|
|
}
|