You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

153 lines
5.7 KiB

import json
import hashlib
import random
import secrets
import logging
from datetime import datetime, timedelta
from typing import Optional
from urllib.parse import parse_qsl, urlencode, urlparse, urlunparse
from redis.exceptions import RedisError
from django.conf import settings
from config.redis_config import RedisConfig
from utils.exceptions import ServiceUnavailableException, NotFoundException
logger = logging.getLogger(__name__)
class RedisManager(RedisConfig):
def __serialize(self, code, fullname, password):
return f'{code},{fullname},{password}'
def add_to_redis(self, code, **kwargs) -> bool:
try:
password = kwargs.get('password')
key = self.__serialize(
code=code, fullname=kwargs['fullname'], password=password
)
self.redis.set(kwargs["email"], str(key), ex=timedelta(minutes=20))
return kwargs["email"]
except RedisError as exp:
raise ServiceUnavailableException()
def __deserialize(
self,
value: str,
key: list = ['code', 'fullname', 'password']
):
values = value.split(',')
# Check if lengths of keys and values are not equal
if len(key) != len(values):
raise ValueError("The number of keys does not match the number of values.")
result = {}
for k, v in zip(key, values):
if not k or not v: # Check if either key or value is empty
result[k] = None # or '' if you prefer empty string
else:
result[k] = v
return result
def get_by_redis(self, key: str):
try:
print(key)
data = self.redis.get(key)
print(f'get => {data}')
return self.__deserialize(data.decode())
except RedisError as exp:
raise ServiceUnavailableException()
except (TypeError, ValueError, AttributeError):
raise NotFoundException()
def check_exists_redis(self, email: str) -> bool:
"""
check exists key in redis
"""
try:
exists = self.redis.exists(email)
return exists
except RedisError as exp:
raise CustomException("Service temporarily unavailable")
@staticmethod
def generate_otp_code() -> int:
random_code = random.randint(10000, 99999)
return random_code
class OnlineClassTokenManager(RedisConfig):
"""Manage temporary tokens used for joining online classes."""
KEY_PREFIX = "online_class_token:"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.ttl = getattr(settings, "ONLINE_CLASS_TOKEN_TTL", 300)
def _build_key(self, token: str) -> str:
return f"{self.KEY_PREFIX}{token}"
def generate_token(self, course_id: int, user_identifier: str) -> str:
seed = f"{course_id}:{user_identifier}:{secrets.token_urlsafe(16)}"
token = hashlib.sha256(seed.encode()).hexdigest()
logger.info(f"[OnlineClassToken] Token generated - course_id={course_id} user={user_identifier} token={token[:16]}...")
return token
def store_token(self, token: str, payload: dict, ttl: Optional[int] = None) -> None:
data = {
**payload,
"generated_at": datetime.utcnow().isoformat() + "Z",
}
key = self._build_key(token)
ttl_value = ttl or self.ttl
logger.info(f"[OnlineClassToken] Storing token - key={key} ttl={ttl_value}s payload={payload}")
try:
self.redis.set(key, json.dumps(data), ex=ttl_value)
logger.info(f"[OnlineClassToken] Token stored successfully - key={key}")
except RedisError as e:
logger.error(f"[OnlineClassToken] Failed to store token - key={key} error={str(e)}")
raise
def get_payload(self, token: str) -> dict:
key = self._build_key(token)
logger.info(f"[OnlineClassToken] Retrieving token - key={key} token={token[:16]}...")
try:
stored = self.redis.get(key)
if not stored:
logger.warning(f"[OnlineClassToken] Token not found or expired - key={key}")
raise NotFoundException("Token not found or has expired.")
payload = json.loads(stored)
logger.info(f"[OnlineClassToken] Token retrieved successfully - key={key} payload={payload}")
return payload
except RedisError as e:
logger.error(f"[OnlineClassToken] Redis error retrieving token - key={key} error={str(e)}")
raise
except json.JSONDecodeError as e:
logger.error(f"[OnlineClassToken] Invalid JSON in stored token - key={key} error={str(e)}")
raise NotFoundException("Invalid token data.")
def delete_token(self, token: str) -> None:
key = self._build_key(token)
logger.info(f"[OnlineClassToken] Deleting token - key={key}")
try:
result = self.redis.delete(key)
logger.info(f"[OnlineClassToken] Token deleted - key={key} deleted={result}")
except RedisError as e:
logger.error(f"[OnlineClassToken] Failed to delete token - key={key} error={str(e)}")
raise
@staticmethod
def build_entry_url(token: str, base_url: Optional[str] = None) -> str:
base = base_url or getattr(settings, "ONLINE_CLASS_FRONTEND_DOMAIN", getattr(settings, "SITE_DOMAIN", ""))
if not base:
return f"?token={token}"
parsed = urlparse(base)
query_params = dict(parse_qsl(parsed.query))
query_params["token"] = token
new_query = urlencode(query_params)
return urlunparse(parsed._replace(query=new_query))