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:
118
tracker/services/project_service.py
Normal file
118
tracker/services/project_service.py
Normal file
@@ -0,0 +1,118 @@
|
||||
"""Project service for project management."""
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from ..models import Project
|
||||
|
||||
|
||||
_PROJECTS_ROOT = Path("projects")
|
||||
|
||||
|
||||
def get_projects_root() -> Path:
|
||||
"""Return the root directory for all projects."""
|
||||
return _PROJECTS_ROOT
|
||||
|
||||
|
||||
def _get_project_meta_path(slug: str) -> Path:
|
||||
"""Return the path to the project's meta/project.yaml file."""
|
||||
return _PROJECTS_ROOT / slug / "meta" / "project.yaml"
|
||||
|
||||
|
||||
def _get_project_readme_path(slug: str) -> Path:
|
||||
"""Return the path to the project's README.md file."""
|
||||
return _PROJECTS_ROOT / slug / "README.md"
|
||||
|
||||
|
||||
def create_project(
|
||||
name: str,
|
||||
slug: str,
|
||||
description: str = "",
|
||||
type: str = "misc",
|
||||
tags: Optional[list[str]] = None,
|
||||
repo_path: Optional[Path] = None,
|
||||
) -> Project:
|
||||
"""
|
||||
Create a new project and return the Project instance.
|
||||
Note: This does not write any files - that is handled by storage.
|
||||
"""
|
||||
if tags is None:
|
||||
tags = []
|
||||
|
||||
project = Project(
|
||||
id=str(uuid.uuid4()),
|
||||
name=name,
|
||||
slug=slug,
|
||||
description=description,
|
||||
type=type,
|
||||
status="inbox",
|
||||
tags=tags,
|
||||
root_path=_PROJECTS_ROOT / slug,
|
||||
repo_path=repo_path,
|
||||
created_at=datetime.now(),
|
||||
updated_at=datetime.now(),
|
||||
)
|
||||
return project
|
||||
|
||||
|
||||
def get_project(slug: str) -> Optional[Project]:
|
||||
"""
|
||||
Get a project by slug.
|
||||
Note: This reads from file system - placeholder for storage integration.
|
||||
"""
|
||||
meta_path = _get_project_meta_path(slug)
|
||||
if not meta_path.exists():
|
||||
return None
|
||||
# TODO: Load from storage (YAML)
|
||||
return None
|
||||
|
||||
|
||||
def update_project(slug: str, **kwargs) -> Optional[Project]:
|
||||
"""
|
||||
Update a project's attributes.
|
||||
Note: This does not persist - that is handled by storage.
|
||||
"""
|
||||
project = get_project(slug)
|
||||
if project is None:
|
||||
return None
|
||||
|
||||
for key, value in kwargs.items():
|
||||
if hasattr(project, key):
|
||||
setattr(project, key, value)
|
||||
|
||||
project.updated_at = datetime.now()
|
||||
return project
|
||||
|
||||
|
||||
def list_projects() -> list[Project]:
|
||||
"""
|
||||
List all projects.
|
||||
Note: This reads from file system - placeholder for storage integration.
|
||||
"""
|
||||
projects_root = get_projects_root()
|
||||
if not projects_root.exists():
|
||||
return []
|
||||
|
||||
projects = []
|
||||
for item in projects_root.iterdir():
|
||||
if item.is_dir() and not item.name.startswith("."):
|
||||
project = get_project(item.name)
|
||||
if project is not None:
|
||||
projects.append(project)
|
||||
|
||||
return projects
|
||||
|
||||
|
||||
def ensure_project_structure(slug: str) -> None:
|
||||
"""
|
||||
Ensure the project directory structure exists.
|
||||
Creates: sessions/, docs/, assets/, meta/
|
||||
Note: This creates directories only - actual file writing is storage's job.
|
||||
"""
|
||||
project_root = _PROJECTS_ROOT / slug
|
||||
directories = ["sessions", "docs", "assets", "meta"]
|
||||
|
||||
for directory in directories:
|
||||
(project_root / directory).mkdir(parents=True, exist_ok=True)
|
||||
Reference in New Issue
Block a user