i have odoo17 enterprise and fastapi project and we need to add location to sessions list and detail and we get it from ip adress of user that we get from request, heres my files add location and ip adress of user when creating session and write it to session data and give it back in session list and session detail routers:
redis_session.py: import redis
from datetime import datetime, timezone
import json
import uuid
import asyncio
from ..schemas.user_session_schema import UserStatus

class IntegratedAuthSessionManager:
    def __init__(self, redis_url):
        self.redis = redis.Redis(host=redis_url, port=6379, decode_responses=True)
        self.cleanup_interval = 60
        self.activity_timeout = 300  # 5 minutes for idle status
        self.websocket_connections: Dict[str, Set[str]] = {}

    async def start_cleanup_task(self):
        while True:
            await self.cleanup_expired_sessions()
            await self.update_idle_status()
            await asyncio.sleep(self.cleanup_interval)

    async def update_idle_status(self):
        current_time = datetime.now(timezone.utc)
        for key in self.redis.scan_iter("session:*"):
            session_data = self.redis.get(key)
            if session_data:
                session = json.loads(session_data)
                last_activity = datetime.fromisoformat(session['last_activity'])

                if (current_time - last_activity).total_seconds() > self.activity_timeout:
                    session['status'] = UserStatus.IDLE
                    self.redis.set(key, json.dumps(session))

    async def cleanup_expired_sessions(self):
        current_time = datetime.now(timezone.utc).isoformat()
        for key in self.redis.scan_iter("session:*"):
            session_data = self.redis.get(key)
            if session_data:
                session = json.loads(session_data)
                if session['expires_at'] <= current_time:
                    self.end_session(key.decode('utf-8').split(':')[1])

    def create_session(self, user_data):
        user_id = user_data['user_id']
        session_id = str(uuid.uuid4())

        session_data = {
            'user_id': user_id,
            'device_name': user_data['device_name'],
            'device_id': user_data['device_id'],
            'expires_at': user_data['expires_at'].isoformat(),
            'app_version': user_data.get('app_version'),
            'location': user_data.get('location'),
            'last_activity': datetime.now(timezone.utc).isoformat(),
            'device_unique_name': user_data.get('device_unique_name'),
            'is_ios': user_data.get('is_ios'),
            'status': UserStatus.ONLINE,
            'lock_timestamp': datetime.now(timezone.utc).isoformat()
        }

        self.redis.setex(
            f"session:{session_id}",
            int(user_data['expires_at'].timestamp()) - int(datetime.now(timezone.utc).timestamp()),
            json.dumps(session_data)
        )
        self.redis.sadd(f"user_sessions:{user_id}", session_id)

        return session_id

        # Store the session data with expiration
        self.redis.setex(f"session:{session_id}", int(user_data['expires_at'].timestamp()) - int(datetime.now(timezone.utc).timestamp()), json.dumps(session_data))

        # Add this session to the user's set of active sessions
        self.redis.sadd(f"user_sessions:{user_id}", session_id)

        return session_id

    def get_session(self, session_id):
        session_data = self.redis.get(f"session:{session_id}")
        if not session_data:
            return None

        session = json.loads(session_data)

        # Check if the session has expired
        current_time = datetime.now(timezone.utc).isoformat()
        if current_time > session['expires_at']:
            self.end_session(session_id)
            return None

        return session

    def end_session(self, session_id):
        session_data = self.redis.get(f"session:{session_id}")
        if session_data:
            session = json.loads(session_data)
            user_id = session['user_id']
            self.redis.delete(f"session:{session_id}")
            self.redis.srem(f"user_sessions:{user_id}", session_id)

    def update_last_activity(self, session_id):
        session_data = self.get_session(session_id)
        if session_data:
            session_data['last_activity'] = datetime.now(timezone.utc).isoformat()
            self.redis.set(f"session:{session_id}", json.dumps(session_data))

    def update_session(self, session_id, user_data):
        session_data = self.get_session(session_id)
        if session_data:
            session_data.update(user_data)
            self.redis.set(f"session:{session_id}", json.dumps(session_data))


    def get_sessions_by_user_and_device(self, user_id, device_id):
        """
        Retrieve all active sessions for a specific user and device.
        """
        # Get all session IDs associated with the user
        session_ids = self.redis.smembers(f"user_sessions:{user_id}")
        active_sessions = []

        # Iterate over all session IDs and filter by device_id
        for session_id in session_ids:
            session_data = self.get_session(session_id)
            if session_data:
                # Check if the session belongs to the given device_id
                if session_data.get('device_id') == device_id:
                    active_sessions.append(session_id)

        return active_sessions

    def update_session_status(self, session_id: str, status: UserStatus):
        session_data = self.get_session(session_id)
        if session_data:
            session_data['status'] = status
            session_data['last_activity'] = datetime.now(timezone.utc).isoformat()
            self.redis.set(f"session:{session_id}", json.dumps(session_data))

    def register_websocket(self, session_id: str, websocket_id: str):
        if session_id not in self.websocket_connections:
            self.websocket_connections[session_id] = set()
        self.websocket_connections[session_id].add(websocket_id)
        self.update_session_status(session_id, UserStatus.ONLINE)

    def unregister_websocket(self, session_id: str, websocket_id: str):
        if session_id in self.websocket_connections:
            self.websocket_connections[session_id].discard(websocket_id)
            if not self.websocket_connections[session_id]:
                self.update_session_status(session_id, UserStatus.OFFLINE)
                del self.websocket_connections[session_id]

redis_session_manager = IntegratedAuthSessionManager("redis")





user_session_router - from fastapi import APIRouter, HTTPException, Depends, WebSocket, WebSocketDisconnect
import asyncio
from datetime import timedelta, timezone
from typing import Annotated
from odoo.api import Environment
from odoo import _
from ..dependencies import odoo_env, get_partner_with_session, get_partner_with_session_id
from ..schemas.user_session_schema import UserStatus
from odoo.addons.base.models.res_partner import Partner
from odoo.addons.fastapi_addon.utils.redis_session import redis_session_manager
from odoo.addons.fastapi_addon.schemas.user_session_schema import (
    SessionSchema,
    SessionResponse,
)
from datetime import datetime

router = APIRouter(tags=['User Session'])



@router.get("/sessions", response_model=SessionResponse)
async def get_sessions(
    env: Annotated[Environment, Depends(odoo_env)],
    partner: Annotated[Partner, Depends(get_partner_with_session)],
    current_session_id: str = Depends(get_partner_with_session_id)
):
    user_id = partner.id
    session_ids = redis_session_manager.redis.smembers(f"user_sessions:{user_id}")

    current_session_data = redis_session_manager.get_session(current_session_id)
    if not current_session_data:
        raise HTTPException(status_code=404, detail=_("Current session not found"))

    response = SessionResponse(
        current_session=SessionSchema(
            session_id=current_session_id,
            app_version=current_session_data.get('app_version'),
            device_name=current_session_data['device_name'],
            location=current_session_data.get('location'),
            last_activity=current_session_data.get('last_activity'),
            is_ios=current_session_data.get('is_ios') or False,
            status=current_session_data.get('status', UserStatus.OFFLINE),
            lock_timestamp=current_session_data.get('lock_timestamp') or datetime.now(timezone.utc)
        ),
        other_sessions=[]
    )

    for session_id in session_ids:
        if session_id != current_session_id:
            session_data = redis_session_manager.get_session(session_id)
            if session_data:
                session_info = SessionSchema(
                    session_id=session_id,
                    app_version=session_data.get('app_version'),
                    device_name=session_data['device_name'],
                    location=session_data.get('location'),
                    last_activity=session_data.get('last_activity'),
                    is_ios=session_data.get('is_ios') or False,
                    status=session_data.get('status', UserStatus.OFFLINE),
                    lock_timestamp=session_data.get('lock_timestamp') or datetime.now(timezone.utc)
                )
                response.other_sessions.append(session_info)

    return response


@router.get("/session/{session_id}", response_model=SessionSchema)
async def get_session_detail(
        session_id: str,
        env: Annotated[Environment, Depends(odoo_env)],
        partner: Annotated[Partner, Depends(get_partner_with_session)]
):
    
    user_id = partner.id
    user_sessions = redis_session_manager.redis.smembers(f"user_sessions:{user_id}")

    if session_id not in user_sessions:
        raise HTTPException(status_code=404, detail=_("Session not found"))

    session_data = redis_session_manager.get_session(session_id)
    if not session_data:
        raise HTTPException(status_code=404, detail=_("Session data not found"))
    session_detail = SessionSchema(
        session_id=session_id,
        app_version=session_data.get('app_version'),
        device_name=session_data['device_name'],
        location=session_data.get('location'),
        last_activity=session_data.get('last_activity'),
        is_ios=session_data.get('is_ios') or False,
        status=session_data.get('status', UserStatus.OFFLINE),
        lock_timestamp=session_data.get('lock_timestamp') if 'lock_timestamp' in session_data else None,
    )

    return session_detail


session_schema.py:
from pydantic import BaseModel
from typing import List, Optional
from datetime import datetime
from enum import Enum


class UserStatus(str, Enum):
    ONLINE = "online"
    OFFLINE = "offline"
    IDLE = "idle"


class SessionSchema(BaseModel):
    session_id: str
    app_version: str | None
    device_name: str | None
    location: str | None
    last_activity: str | None
    is_ios: bool | None
    status: UserStatus = UserStatus.OFFLINE
    lock_timestamp: Optional[datetime] | None


class SessionResponse(BaseModel):
    current_session: SessionSchema
    other_sessions: List[SessionSchema] 
    

ip_geolocation.py:
# ip_geolocation.py
import requests
from typing import Optional, Dict
import asyncio


def get_location_from_ip_sync(ip_address: str) -> Optional[Dict[str, str]]:
    """
    Synchronously fetches location data for a given IP address using ip-api.com.

    Args:
        ip_address (str): The IP address of the user.

    Returns:
        Optional[Dict[str, str]]: A dictionary containing location details if successful, else None.
    """
    url = f"http://ip-api.com/json/{ip_address}"
    try:
        response = requests.get(url, timeout=5)
        data = response.json()
        if data.get("status") == "success":
            location = {
                "country": data.get("country"),
                "regionName": data.get("regionName"),
                "city": data.get("city"),
                "zip": data.get("zip"),
                "lat": data.get("lat"),
                "lon": data.get("lon"),
                "timezone": data.get("timezone"),
                "isp": data.get("isp"),
                "org": data.get("org"),
                "as": data.get("as"),
            }
            return location
        else:
            return None
    except Exception as e:
        return None


async def get_location_from_ip(ip_address: str) -> Optional[Dict[str, str]]:
    """
    Asynchronously fetches location data for a given IP address using ip-api.com.

    Args:
        ip_address (str): The IP address of the user.

    Returns:
        Optional[Dict[str, str]]: A dictionary containing location details if successful, else None.
    """
    loop = asyncio.get_event_loop()
    return await loop.run_in_executor(None, get_location_from_ip_sync, ip_address)
    

user.py: auth method

    @api.model
    def commeta_authenticate(self, token_data, device_data):
        if not token_data:
            return {'error': 'No token data provided'}

        user_info = self.commeta_get_user_info(token_data['access_token'])
        if not user_info:
            return {'error': 'Failed to get user info'}



        user = self.sudo().search([('phone_number', '=', user_info['phone'])], limit=1)
        profile = user_info['profile']

        avatar_data = None
        if 'avatar' in profile and profile['avatar'] and (not user or not user.avatar):
            try:
                response = requests.get(profile['avatar'])
                if response.status_code == 200:
                    avatar_data = base64.b64encode(BytesIO(response.content).read()).decode('utf-8')
            except requests.RequestException as e:
                _logger.error(f"Failed to download avatar: {e}")

        if not user:
            # Create a new user if none exists
            user_data = {
                'first_name': profile['first_name'],
                'name': profile['first_name'],
                'last_name': profile['last_name'],
                'middle_name': profile['middle_name'],
                'phone_number': user_info['phone'],
                'birth_date': profile['birth_date'],
                # 'avatar': profile['avatar'],  # TODO avatar for user
                'gender': 'male' if profile['gender'] == 'M' else 'female',
                'last_login': datetime.now(),
                'pnfl': profile['pinfl'],
            }
            if avatar_data:
                user_data['avatar'] = avatar_data
            user = self.sudo().create(user_data)
        else:
            # Update only missing or outdated fields
            profile = user_info['profile']
            update_fields = {}

            try:
                if not user.first_name:
                    update_fields['first_name'] = profile['first_name']
                    update_fields['name'] = profile['first_name']

                if not user.last_name:
                    update_fields['last_name'] = profile['last_name']

                if not user.middle_name:
                    update_fields['middle_name'] = profile['middle_name']

                if not user.birth_date:
                    update_fields['birth_date'] = profile['birth_date']

                if not user.avatar and avatar_data:
                    update_fields['avatar'] = avatar_data # TODO Avatar For user

                if not user.gender:
                    update_fields['gender'] = 'male' if profile['gender'] == 'M' else 'female'

                if not user.pnfl:
                    update_fields['pnfl'] = profile['pinfl']
            except KeyError as e:
                return {'error': f"Missing required field in profile: {e}"}

            if update_fields:
                update_fields['last_login'] = datetime.now()  # Update the last login time
                user.write(update_fields)

        device_user = self.env['user.device'].sudo().search([
            ('partner_id', '=', user.id),
            ('device_name', '=', device_data.get('device_name')),
            ('device_unique_name', '=', device_data.get('device_unique_name')),
        ])
        if not device_user:
            device_user = self.env['user.device'].sudo().create({
                'partner_id': user.id,
                'device_name': device_data.get('device_name'),
                'device_unique_name': device_data.get('device_unique_name'),
                'is_ios': device_data.get('is_ios'),
            })

        existing_sessions = redis_session_manager.get_sessions_by_user_and_device(user.id, device_user.id)

        if existing_sessions:
            session_id = existing_sessions[0]  # Assuming sessions are stored as a list
            session_data = redis_session_manager.get_session(session_id)
        else:
            # Create a new session if no existing session is found
            user_data = {
                'user_id': user.id,
                'expires_at': datetime.now(timezone.utc) + timedelta(days=365),
                'device_name': device_data.get('device_name'),
                'device_id': device_user.id,
                'device_unique_name': device_data.get('device_unique_name'),
                'is_ios': device_data.get('is_ios'),
            }
            session_id = redis_session_manager.create_session(user_data=user_data)

        project_token_data = {
            'user_id': user.id,
            'phone': user.phone_number,
            'device_user_id': device_user.id,
            'session_id': session_id
        }
        access_token, refresh_token = self.create_tokens(project_token_data)

        access_token_expire = datetime.now(timezone.utc) + timedelta(days=365)

        return {
            'user_id': user.id,
            "username": user.name,
            'access_token': access_token,
            'refresh_token': refresh_token,
            'expires_at': access_token_expire.isoformat(),
            'is_verified': bool(user.pnfl),
        }