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