Implement storage layer for MVP-1 Personal Tracker CLI
Add storage layer with FileStorage, MarkdownReader, and MarkdownWriter classes. Add data models (Project, Session, Note, Change).
This commit is contained in:
67
tracker/services/session_service.py
Normal file
67
tracker/services/session_service.py
Normal file
@@ -0,0 +1,67 @@
|
||||
"""Session service for active session management."""
|
||||
|
||||
import json
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from ..models import Session
|
||||
|
||||
|
||||
_ACTIVE_SESSION_FILE = ".active_session.json"
|
||||
|
||||
|
||||
def get_active_session_path() -> Path:
|
||||
"""Return the path to the active session file in projects/ directory."""
|
||||
return Path("projects") / _ACTIVE_SESSION_FILE
|
||||
|
||||
|
||||
def get_active_session() -> Optional[Session]:
|
||||
"""Load and return the currently active session, or None if none exists."""
|
||||
path = get_active_session_path()
|
||||
if not path.exists():
|
||||
return None
|
||||
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
|
||||
# Convert started_at string back to datetime
|
||||
data["started_at"] = datetime.fromisoformat(data["started_at"])
|
||||
if data.get("ended_at"):
|
||||
data["ended_at"] = datetime.fromisoformat(data["ended_at"])
|
||||
|
||||
return Session(**data)
|
||||
|
||||
|
||||
def set_active_session(session: Session) -> None:
|
||||
"""Save the given session as the active session."""
|
||||
path = get_active_session_path()
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
data = session.model_dump(mode="json")
|
||||
# Serialize datetime objects to ISO format
|
||||
data["started_at"] = session.started_at.isoformat()
|
||||
if session.ended_at:
|
||||
data["ended_at"] = session.ended_at.isoformat()
|
||||
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
def clear_active_session() -> None:
|
||||
"""Remove the active session file."""
|
||||
path = get_active_session_path()
|
||||
if path.exists():
|
||||
path.unlink()
|
||||
|
||||
|
||||
def validate_no_other_active_session(project_slug: str) -> bool:
|
||||
"""
|
||||
Check if there is an active session for a different project.
|
||||
Returns True if no conflict exists (i.e., either no active session
|
||||
or the active session belongs to the same project).
|
||||
"""
|
||||
active = get_active_session()
|
||||
if active is None:
|
||||
return True
|
||||
return active.project_slug == project_slug
|
||||
Reference in New Issue
Block a user