This commit is contained in:
170
src/storage/models.py
Normal file
170
src/storage/models.py
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
"""Data models for DevTrace."""
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import Optional, Dict, Any
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Session:
|
||||||
|
"""Represents a development session."""
|
||||||
|
id: int
|
||||||
|
name: str
|
||||||
|
directory: str
|
||||||
|
start_time: Optional[datetime] = None
|
||||||
|
end_time: Optional[datetime] = None
|
||||||
|
notes: Optional[str] = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def duration(self) -> Optional[float]:
|
||||||
|
"""Calculate session duration in seconds."""
|
||||||
|
if self.start_time and self.end_time:
|
||||||
|
return (self.end_time - self.start_time).total_seconds()
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class FileEvent:
|
||||||
|
"""Represents a file system event."""
|
||||||
|
id: int
|
||||||
|
session_id: int
|
||||||
|
event_type: str
|
||||||
|
file_path: str
|
||||||
|
timestamp: Optional[datetime] = None
|
||||||
|
details: Optional[str] = None
|
||||||
|
content_hash: Optional[str] = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def filename(self) -> str:
|
||||||
|
"""Get just the filename from the path."""
|
||||||
|
return self.file_path.split("/")[-1]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def extension(self) -> str:
|
||||||
|
"""Get the file extension."""
|
||||||
|
return self.filename.split(".")[-1] if "." in self.filename else ""
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class CommandEvent:
|
||||||
|
"""Represents a shell command execution."""
|
||||||
|
id: int
|
||||||
|
session_id: int
|
||||||
|
command: str
|
||||||
|
timestamp: Optional[datetime] = None
|
||||||
|
exit_code: Optional[int] = None
|
||||||
|
working_directory: Optional[str] = None
|
||||||
|
details: Optional[str] = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def was_successful(self) -> Optional[bool]:
|
||||||
|
"""Check if command was successful."""
|
||||||
|
if self.exit_code is None:
|
||||||
|
return None
|
||||||
|
return self.exit_code == 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def short_command(self) -> str:
|
||||||
|
"""Get truncated command for display."""
|
||||||
|
max_len = 50
|
||||||
|
if len(self.command) <= max_len:
|
||||||
|
return self.command
|
||||||
|
return self.command[:max_len] + "..."
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class GitEvent:
|
||||||
|
"""Represents a git operation."""
|
||||||
|
id: int
|
||||||
|
session_id: int
|
||||||
|
operation: str
|
||||||
|
branch: Optional[str] = None
|
||||||
|
commit_hash: Optional[str] = None
|
||||||
|
timestamp: Optional[datetime] = None
|
||||||
|
details: Optional[str] = None
|
||||||
|
diff: Optional[str] = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def short_hash(self) -> str:
|
||||||
|
"""Get short commit hash."""
|
||||||
|
if self.commit_hash:
|
||||||
|
return self.commit_hash[:7]
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SearchResult:
|
||||||
|
"""Represents a search result."""
|
||||||
|
event_type: str
|
||||||
|
event_id: int
|
||||||
|
score: float
|
||||||
|
details: Dict[str, Any]
|
||||||
|
content: str = ""
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TimelineEvent:
|
||||||
|
"""Unified event representation for timeline display."""
|
||||||
|
id: int
|
||||||
|
session_id: int
|
||||||
|
event_type: str
|
||||||
|
timestamp: Optional[datetime]
|
||||||
|
details: Dict[str, Any]
|
||||||
|
display_text: str
|
||||||
|
icon: str
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_file_event(cls, event: FileEvent) -> "TimelineEvent":
|
||||||
|
"""Create TimelineEvent from FileEvent."""
|
||||||
|
icons = {
|
||||||
|
"created": "📄",
|
||||||
|
"modified": "✏️",
|
||||||
|
"deleted": "🗑️",
|
||||||
|
"moved": "📁"
|
||||||
|
}
|
||||||
|
return cls(
|
||||||
|
id=event.id,
|
||||||
|
session_id=event.session_id,
|
||||||
|
event_type="file",
|
||||||
|
timestamp=event.timestamp,
|
||||||
|
details={"file_path": event.file_path, "content_hash": event.content_hash},
|
||||||
|
display_text=f"{event.event_type} {event.filename}",
|
||||||
|
icon=icons.get(event.event_type, "📄")
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_command_event(cls, event: CommandEvent) -> "TimelineEvent":
|
||||||
|
"""Create TimelineEvent from CommandEvent."""
|
||||||
|
status = "✅" if event.was_successful else "❌" if event.was_successful is False else "⏳"
|
||||||
|
return cls(
|
||||||
|
id=event.id,
|
||||||
|
session_id=event.session_id,
|
||||||
|
event_type="command",
|
||||||
|
timestamp=event.timestamp,
|
||||||
|
details={"command": event.command, "exit_code": event.exit_code},
|
||||||
|
display_text=f"{event.short_command}",
|
||||||
|
icon=f"⚡ {status}"
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_git_event(cls, event: GitEvent) -> "TimelineEvent":
|
||||||
|
"""Create TimelineEvent from GitEvent."""
|
||||||
|
icons = {
|
||||||
|
"commit": "💾",
|
||||||
|
"branch": "🌿",
|
||||||
|
"merge": "🔀",
|
||||||
|
"checkout": "🔀",
|
||||||
|
"push": "📤",
|
||||||
|
"pull": "📥",
|
||||||
|
"rebase": "📜",
|
||||||
|
"reset": "⏪"
|
||||||
|
}
|
||||||
|
return cls(
|
||||||
|
id=event.id,
|
||||||
|
session_id=event.session_id,
|
||||||
|
event_type="git",
|
||||||
|
timestamp=event.timestamp,
|
||||||
|
details={"operation": event.operation, "branch": event.branch, "commit": event.commit_hash},
|
||||||
|
display_text=f"{event.operation} {event.branch or event.short_hash}",
|
||||||
|
icon=icons.get(event.operation, "🔧")
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user