rdune71's picture
Implement Goal Tracker services with Redis-backed CRUD operations
f6d2b4a
import uuid
from datetime import datetime
from typing import List, Optional
from core.memory import get_redis_client
from .models import GoalCreate, GoalUpdate, GoalInDB
def _get_redis_key(user_id: str, goal_id: str) -> str:
return f"user:{user_id}:goals:{goal_id}"
def _get_index_key(user_id: str) -> str:
return f"user:{user_id}:goals:index"
def create_goal(user_id: str, goal_data: GoalCreate) -> GoalInDB:
redis_client = get_redis_client()
if not redis_client:
raise RuntimeError("Redis connection unavailable.")
goal_id = str(uuid.uuid4())
now = datetime.utcnow()
goal_dict = {
"id": goal_id,
"user_id": user_id,
"title": goal_data.title,
"description": goal_data.description or "",
"target_date": goal_data.target_date.isoformat() if goal_data.target_date else "",
"created_at": now.isoformat(),
"updated_at": now.isoformat()
}
redis_client.hset(_get_redis_key(user_id, goal_id), mapping=goal_dict)
redis_client.zadd(_get_index_key(user_id), {goal_id: now.timestamp()})
return GoalInDB(**goal_dict)
def get_goals(user_id: str) -> List[GoalInDB]:
redis_client = get_redis_client()
if not redis_client:
return []
goal_ids = redis_client.zrange(_get_index_key(user_id), 0, -1)
goals = []
for gid in goal_ids:
raw_goal = redis_client.hgetall(_get_redis_key(user_id, gid))
if raw_goal:
# Parse dates back to datetime objects
raw_goal["target_date"] = datetime.fromisoformat(raw_goal["target_date"]) if raw_goal["target_date"] else None
raw_goal["created_at"] = datetime.fromisoformat(raw_goal["created_at"])
raw_goal["updated_at"] = datetime.fromisoformat(raw_goal["updated_at"])
goals.append(GoalInDB(**raw_goal))
return goals
def get_goal_by_id(user_id: str, goal_id: str) -> Optional[GoalInDB]:
redis_client = get_redis_client()
if not redis_client:
return None
raw_goal = redis_client.hgetall(_get_redis_key(user_id, goal_id))
if not raw_goal:
return None
raw_goal["target_date"] = datetime.fromisoformat(raw_goal["target_date"]) if raw_goal["target_date"] else None
raw_goal["created_at"] = datetime.fromisoformat(raw_goal["created_at"])
raw_goal["updated_at"] = datetime.fromisoformat(raw_goal["updated_at"])
return GoalInDB(**raw_goal)
def update_goal(user_id: str, goal_id: str, updates: GoalUpdate) -> Optional[GoalInDB]:
redis_client = get_redis_client()
if not redis_client:
return None
raw_goal = redis_client.hgetall(_get_redis_key(user_id, goal_id))
if not raw_goal:
return None
# Apply updates
update_data = updates.dict(exclude_unset=True)
for field, value in update_data.items():
if isinstance(value, datetime):
raw_goal[field] = value.isoformat()
else:
raw_goal[field] = value
raw_goal["updated_at"] = datetime.utcnow().isoformat()
redis_client.hset(_get_redis_key(user_id, goal_id), mapping=raw_goal)
parsed_goal = raw_goal.copy()
parsed_goal["target_date"] = datetime.fromisoformat(parsed_goal["target_date"]) if parsed_goal["target_date"] else None
parsed_goal["created_at"] = datetime.fromisoformat(parsed_goal["created_at"])
parsed_goal["updated_at"] = datetime.fromisoformat(parsed_goal["updated_at"])
return GoalInDB(**parsed_goal)
def delete_goal(user_id: str, goal_id: str) -> bool:
redis_client = get_redis_client()
if not redis_client:
return False
pipe = redis_client.pipeline()
pipe.delete(_get_redis_key(user_id, goal_id))
pipe.zrem(_get_index_key(user_id), goal_id)
results = pipe.execute()
return results[0] > 0 # True if deleted