Update backend con TTL e sliding
This commit is contained in:
@@ -20,6 +20,7 @@ async def create_session_endpoint(
|
||||
meta = redis_service.create_session(user_id, first_message)
|
||||
return meta
|
||||
|
||||
|
||||
@router.get("/sessions", response_model=List[dict])
|
||||
async def list_sessions_endpoint(
|
||||
user_id: str = Query(..., description="User ID")
|
||||
@@ -29,6 +30,7 @@ async def list_sessions_endpoint(
|
||||
"""
|
||||
return redis_service.get_sessions(user_id)
|
||||
|
||||
|
||||
@router.get("/sessions/{session_id}", response_model=dict)
|
||||
async def get_session_meta_endpoint(
|
||||
user_id: str = Query(..., description="User ID"),
|
||||
@@ -39,6 +41,7 @@ async def get_session_meta_endpoint(
|
||||
"""
|
||||
return redis_service.get_session_meta(user_id, session_id) or {}
|
||||
|
||||
|
||||
@router.patch("/sessions/{session_id}", response_model=dict)
|
||||
async def update_session_endpoint(
|
||||
user_id: str = Query(..., description="User ID"),
|
||||
@@ -50,6 +53,7 @@ async def update_session_endpoint(
|
||||
"""
|
||||
return redis_service.update_session_meta(user_id, session_id, session_name=session_name) or {}
|
||||
|
||||
|
||||
@router.delete("/sessions/{session_id}", response_model=dict)
|
||||
async def delete_session_endpoint(
|
||||
user_id: str = Query(..., description="User ID"),
|
||||
@@ -61,13 +65,36 @@ async def delete_session_endpoint(
|
||||
redis_service.delete_session(user_id, session_id)
|
||||
return {"status": "deleted"}
|
||||
|
||||
# estensione TTL sessioni
|
||||
|
||||
# -------------------------
|
||||
# Gestione TTL sessioni
|
||||
# -------------------------
|
||||
|
||||
@router.post("/sessions/{session_id}/extend_ttl", response_model=dict)
|
||||
async def extend_ttl_endpoint(
|
||||
user_id: str = Query(..., description="User ID"),
|
||||
session_id: str = Path(..., description="Session ID")
|
||||
extra_seconds: int = Body(..., embed=True)
|
||||
session_id: str = Path(..., description="Session ID"),
|
||||
extra_seconds: int = Body(..., embed=True, description="Seconds to extend the TTL")
|
||||
):
|
||||
"""
|
||||
Extend the TTL (time-to-live) for a given session and its related data.
|
||||
"""
|
||||
redis_service.extend_session_ttl(user_id, session_id, extra_seconds)
|
||||
return {"status": "ttl_extended"}
|
||||
return {"status": "ttl_extended", "extra_seconds": extra_seconds}
|
||||
|
||||
|
||||
@router.post("/sessions/{session_id}/refresh_ttl", response_model=dict)
|
||||
async def refresh_ttl_endpoint(
|
||||
user_id: str = Query(..., description="User ID"),
|
||||
session_id: str = Path(..., description="Session ID")
|
||||
):
|
||||
"""
|
||||
Reset the TTL for a given session and its related data back to the default value.
|
||||
"""
|
||||
redis_service.refresh_session_ttl(user_id, session_id)
|
||||
return {
|
||||
"status": "ttl_refreshed",
|
||||
"default_ttl_seconds": redis_service.DEFAULT_TTL_SECONDS
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# services/redis_service.py
|
||||
|
||||
import redis
|
||||
import json
|
||||
import uuid
|
||||
@@ -6,6 +7,9 @@ from config import settings
|
||||
from datetime import datetime
|
||||
from typing import List, Optional
|
||||
|
||||
# TTL di default: 30 giorni
|
||||
DEFAULT_TTL_SECONDS = 30 * 24 * 60 * 60
|
||||
|
||||
r = redis.Redis(
|
||||
host=settings.REDIS_HOST,
|
||||
port=settings.REDIS_PORT,
|
||||
@@ -13,34 +17,24 @@ r = redis.Redis(
|
||||
decode_responses=True
|
||||
)
|
||||
|
||||
# setup defaults
|
||||
DEFAULT_TTL_SECONDS = 30 * 24 * 3600 # 30 gg
|
||||
|
||||
# -------------------------
|
||||
# Chat message operations
|
||||
# -------------------------
|
||||
|
||||
def save_chat(user_id: str, session_id: str, message: dict):
|
||||
"""
|
||||
Save a message for a user/session, adding a UTC ISO timestamp.
|
||||
Also updates session metadata (message_count, history_size_bytes).
|
||||
"""Save a message for a user/session, adding a UTC ISO timestamp.
|
||||
Also updates session metadata and TTL.
|
||||
"""
|
||||
key = f"chatHistory:{user_id}:{session_id}"
|
||||
enriched = {
|
||||
**message,
|
||||
"timestamp": datetime.utcnow().isoformat() + "Z"
|
||||
}
|
||||
enriched = {**message, "timestamp": datetime.utcnow().isoformat() + "Z"}
|
||||
r.rpush(key, json.dumps(enriched))
|
||||
r.expire(key, DEFAULT_TTL_SECONDS) # TTL anche per la history
|
||||
|
||||
# Update metadata after adding
|
||||
r.expire(key, DEFAULT_TTL_SECONDS) # rinnova TTL history
|
||||
_update_session_stats(user_id, session_id)
|
||||
_refresh_ttl(user_id, session_id) # rinnova TTL meta e indice
|
||||
|
||||
|
||||
def get_chat(user_id: str, session_id: str, limit: Optional[int] = None):
|
||||
"""
|
||||
Retrieve messages for a user/session, sorted by timestamp.
|
||||
Optionally limit to the last N messages.
|
||||
"""
|
||||
"""Retrieve messages for a user/session, sorted by timestamp."""
|
||||
key = f"chatHistory:{user_id}:{session_id}"
|
||||
data = r.lrange(key, 0, -1)
|
||||
messages = [json.loads(item) for item in data]
|
||||
@@ -51,13 +45,11 @@ def get_chat(user_id: str, session_id: str, limit: Optional[int] = None):
|
||||
|
||||
|
||||
def clear_chat(user_id: str, session_id: str):
|
||||
"""
|
||||
Delete all messages for a user/session.
|
||||
Also resets metadata stats (message_count, history_size_bytes).
|
||||
"""
|
||||
"""Delete all messages for a user/session and reset stats."""
|
||||
key = f"chatHistory:{user_id}:{session_id}"
|
||||
r.delete(key)
|
||||
_update_session_stats(user_id, session_id, reset=True)
|
||||
_refresh_ttl(user_id, session_id)
|
||||
|
||||
|
||||
# -------------------------
|
||||
@@ -65,9 +57,7 @@ def clear_chat(user_id: str, session_id: str):
|
||||
# -------------------------
|
||||
|
||||
def create_session(user_id: str, first_message: str) -> dict:
|
||||
"""
|
||||
Create a new session with initial metadata.
|
||||
"""
|
||||
"""Create a new session with initial metadata and TTL."""
|
||||
session_id = str(uuid.uuid4())
|
||||
created_at = datetime.utcnow().isoformat() + "Z"
|
||||
session_name = (first_message.strip()[:50] or "New Chat")
|
||||
@@ -83,14 +73,13 @@ def create_session(user_id: str, first_message: str) -> dict:
|
||||
|
||||
r.set(meta_key, json.dumps(meta), ex=DEFAULT_TTL_SECONDS)
|
||||
r.zadd(index_key, {session_id: datetime.utcnow().timestamp()})
|
||||
r.expire(index_key, DEFAULT_TTL_SECONDS) # TTL anche per l’indice utente
|
||||
r.expire(index_key, DEFAULT_TTL_SECONDS)
|
||||
|
||||
return meta
|
||||
|
||||
|
||||
def get_sessions(user_id: str) -> List[dict]:
|
||||
"""
|
||||
Return all sessions for a user, newest first.
|
||||
"""
|
||||
"""Return all sessions for a user, newest first."""
|
||||
session_ids = r.zrevrangebyscore(f"chatSessionsIndex:{user_id}", "+inf", "-inf")
|
||||
sessions = []
|
||||
for sid in session_ids:
|
||||
@@ -106,31 +95,35 @@ def get_session_meta(user_id: str, session_id: str) -> Optional[dict]:
|
||||
|
||||
|
||||
def update_session_meta(user_id: str, session_id: str, **updates) -> Optional[dict]:
|
||||
"""
|
||||
Update metadata fields (e.g., session_name) for a session.
|
||||
"""
|
||||
"""Update metadata fields (e.g., session_name) for a session."""
|
||||
meta = get_session_meta(user_id, session_id)
|
||||
if not meta:
|
||||
return None
|
||||
meta.update(updates)
|
||||
r.set(f"chatSession:{user_id}:{session_id}", json.dumps(meta))
|
||||
r.set(f"chatSession:{user_id}:{session_id}", json.dumps(meta), ex=DEFAULT_TTL_SECONDS)
|
||||
_refresh_ttl(user_id, session_id)
|
||||
return meta
|
||||
|
||||
|
||||
def delete_session(user_id: str, session_id: str):
|
||||
"""
|
||||
Delete metadata, chat history, and remove from index.
|
||||
"""
|
||||
"""Delete metadata, chat history, and remove from index."""
|
||||
r.delete(f"chatSession:{user_id}:{session_id}")
|
||||
r.delete(f"chatHistory:{user_id}:{session_id}")
|
||||
r.zrem(f"chatSessionsIndex:{user_id}", session_id)
|
||||
|
||||
def extend_session_ttl(user_id: str, session_id: str, extra_seconds: int):
|
||||
meta_key = f"chatSession:{user_id}:{session_id}"
|
||||
history_key = f"chatHistory:{user_id}:{session_id}"
|
||||
index_key = f"chatSessionsIndex:{user_id}"
|
||||
|
||||
for key in [meta_key, history_key, index_key]:
|
||||
# -------------------------
|
||||
# TTL management
|
||||
# -------------------------
|
||||
|
||||
def extend_session_ttl(user_id: str, session_id: str, extra_seconds: int):
|
||||
"""Extend TTL for session metadata, history, and index."""
|
||||
keys = [
|
||||
f"chatSession:{user_id}:{session_id}",
|
||||
f"chatHistory:{user_id}:{session_id}",
|
||||
f"chatSessionsIndex:{user_id}"
|
||||
]
|
||||
for key in keys:
|
||||
if r.exists(key):
|
||||
current_ttl = r.ttl(key)
|
||||
if current_ttl > 0:
|
||||
@@ -138,29 +131,35 @@ def extend_session_ttl(user_id: str, session_id: str, extra_seconds: int):
|
||||
else:
|
||||
r.expire(key, extra_seconds)
|
||||
|
||||
def refresh_session_ttl(user_id: str, session_id: str):
|
||||
"""Reset TTL for session metadata, history, and index to default."""
|
||||
keys = [
|
||||
f"chatSession:{user_id}:{session_id}",
|
||||
f"chatHistory:{user_id}:{session_id}",
|
||||
f"chatSessionsIndex:{user_id}"
|
||||
]
|
||||
for key in keys:
|
||||
if r.exists(key):
|
||||
r.expire(key, DEFAULT_TTL_SECONDS)
|
||||
|
||||
|
||||
# -------------------------
|
||||
# Internal helpers
|
||||
# -------------------------
|
||||
|
||||
def _update_session_stats(user_id: str, session_id: str, reset: bool = False):
|
||||
"""
|
||||
Refresh message_count and history_size_bytes in metadata.
|
||||
"""
|
||||
"""Refresh message_count and history_size_bytes in metadata."""
|
||||
meta = get_session_meta(user_id, session_id)
|
||||
if not meta:
|
||||
return
|
||||
|
||||
if reset:
|
||||
meta["message_count"] = 0
|
||||
meta["history_size_bytes"] = 0
|
||||
else:
|
||||
# Count messages and total size
|
||||
key = f"chatHistory:{user_id}:{session_id}"
|
||||
messages = r.lrange(key, 0, -1)
|
||||
meta["message_count"] = len(messages)
|
||||
meta["history_size_bytes"] = sum(len(m.encode("utf-8")) for m in messages)
|
||||
|
||||
r.set(f"chatSession:{user_id}:{session_id}", json.dumps(meta))
|
||||
r.set(f"chatSession:{user_id}:{session_id}", json.dumps(meta), ex=DEFAULT_TTL_SECONDS)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user