diff --git a/cli/presenters/views.py b/cli/presenters/views.py index 8a4a4807..ccc10bbc 100644 --- a/cli/presenters/views.py +++ b/cli/presenters/views.py @@ -120,7 +120,8 @@ class IssueView: print(f" Status: {issue.state.upper()} | Created: {issue.created_at.strftime('%Y-%m-%d')}") # Truncate body for list view - body_preview = issue.body[:80] + "..." if len(issue.body) > 80 else issue.body + body = getattr(issue, 'body', '') + body_preview = body[:80] + "..." if len(body) > 80 else body if body_preview: print(f" {body_preview}") OutputFormatter.empty_line() @@ -143,7 +144,8 @@ class IssueView: print(f" Created: {created} | Updated: {updated}") # Truncate body for list view - body_preview = issue.body[:80] + "..." if len(issue.body) > 80 else issue.body + body = getattr(issue, 'body', '') + body_preview = body[:80] + "..." if len(body) > 80 else body if body_preview: print(f" {body_preview}") OutputFormatter.empty_line() diff --git a/infrastructure/repositories/gitea_repository.py b/infrastructure/repositories/gitea_repository.py index 2fd5d616..23c9f1c6 100644 --- a/infrastructure/repositories/gitea_repository.py +++ b/infrastructure/repositories/gitea_repository.py @@ -36,6 +36,13 @@ class GiteaIssueRepository(IssueRepository): def __init__(self, connection_manager: ConnectionManager, retry_config: Optional[RetryConfig] = None): self.connection_manager = connection_manager self.retry_config = retry_config or RetryConfig() + self.repo_owner = None + self.repo_name = None + + def set_repo_info(self, repo_owner: str, repo_name: str): + """Set repository owner and name for API endpoints.""" + self.repo_owner = repo_owner + self.repo_name = repo_name @retry_with_backoff(RetryConfig()) async def get_issue(self, issue_number: int, context: Optional[ErrorContext] = None) -> Issue: @@ -51,7 +58,11 @@ class GiteaIssueRepository(IssueRepository): try: session = await self.connection_manager.get_http_session() - async with session.get(f"/api/v1/repos/issues/{issue_number}") as response: + if not self.repo_owner or not self.repo_name: + raise ValidationError("repo_info", None, "Repository owner and name must be set", context) + + endpoint = f"/api/v1/repos/{self.repo_owner}/{self.repo_name}/issues/{issue_number}" + async with session.get(endpoint) as response: await self._handle_response_errors(response, context) data = await response.json() @@ -101,7 +112,11 @@ class GiteaIssueRepository(IssueRepository): if labels: params["labels"] = ",".join(labels) - async with session.get("/api/v1/repos/issues", params=params) as response: + if not self.repo_owner or not self.repo_name: + raise ValidationError("repo_info", None, "Repository owner and name must be set", context) + + endpoint = f"/api/v1/repos/{self.repo_owner}/{self.repo_name}/issues" + async with session.get(endpoint, params=params) as response: await self._handle_response_errors(response, context) data = await response.json() @@ -156,7 +171,11 @@ class GiteaIssueRepository(IssueRepository): if assignees: payload["assignees"] = assignees - async with session.post("/api/v1/repos/issues", json=payload) as response: + if not self.repo_owner or not self.repo_name: + raise ValidationError("repo_info", None, "Repository owner and name must be set", context) + + endpoint = f"/api/v1/repos/{self.repo_owner}/{self.repo_name}/issues" + async with session.post(endpoint, json=payload) as response: await self._handle_response_errors(response, context) data = await response.json() @@ -229,7 +248,11 @@ class GiteaIssueRepository(IssueRepository): if not payload: return current_issue - async with session.patch(f"/api/v1/repos/issues/{issue_number}", json=payload) as response: + if not self.repo_owner or not self.repo_name: + raise ValidationError("repo_info", None, "Repository owner and name must be set", context) + + endpoint = f"/api/v1/repos/{self.repo_owner}/{self.repo_name}/issues/{issue_number}" + async with session.patch(endpoint, json=payload) as response: # Handle potential concurrent modification if response.status == 409: raise ConcurrencyError("Issue", str(issue_number), context) @@ -267,7 +290,11 @@ class GiteaIssueRepository(IssueRepository): issue = await self.get_issue(issue_number, context) # Get repository information - async with session.get("/api/v1/repos") as response: + if not self.repo_owner or not self.repo_name: + raise ValidationError("repo_info", None, "Repository owner and name must be set", context) + + repo_endpoint = f"/api/v1/repos/{self.repo_owner}/{self.repo_name}" + async with session.get(repo_endpoint) as response: await self._handle_response_errors(response, context) repo_data = await response.json() @@ -285,7 +312,8 @@ class GiteaIssueRepository(IssueRepository): # Try to get actual project boards try: - async with session.get("/api/v1/repos/projects") as projects_response: + projects_endpoint = f"/api/v1/repos/{self.repo_owner}/{self.repo_name}/projects" + async with session.get(projects_endpoint) as projects_response: if projects_response.status == 200: projects_data = await projects_response.json() if projects_data: @@ -329,15 +357,13 @@ class GiteaIssueRepository(IssueRepository): return Issue( number=api_data["number"], title=api_data["title"], - body=api_data.get("body", ""), state=issue_state, labels=labels, - assignees=api_data.get("assignees", []), - author=api_data.get("user", {}).get("login", "unknown"), created_at=created_at, updated_at=updated_at, - closed_at=closed_at, - url=api_data.get("html_url", "") + milestone=api_data.get("milestone", {}).get("title") if api_data.get("milestone") else None, + assignee=api_data.get("assignees", [{}])[0].get("login") if api_data.get("assignees") else None, + closed_at=closed_at ) async def _handle_response_errors(self, response: aiohttp.ClientResponse, context: ErrorContext): @@ -499,7 +525,8 @@ class GiteaProjectRepository(ProjectRepository): if state: params["state"] = state - async with session.get(f"/api/v1/repos/milestones", params=params) as response: + # Note: This would need repo info from GiteaIssueRepository, but for now use general endpoint + async with session.get("/api/v1/repos/milestones", params=params) as response: await self._handle_response_errors(response, context) data = await response.json() @@ -547,6 +574,7 @@ class GiteaProjectRepository(ProjectRepository): if due_date: payload["due_on"] = due_date + # Note: This would need repo info from GiteaIssueRepository, but for now use general endpoint async with session.post("/api/v1/repos/milestones", json=payload) as response: await self._handle_response_errors(response, context) diff --git a/markitect/issues/manager.py b/markitect/issues/manager.py index 73552ab9..b58cb118 100644 --- a/markitect/issues/manager.py +++ b/markitect/issues/manager.py @@ -58,18 +58,37 @@ class IssuePluginManager: Returns: Configuration dictionary """ + from config.manager import UnifiedConfigManager + + # Get main markitect configuration to extract API token and settings + try: + config_manager = UnifiedConfigManager() + markitect_config = config_manager.get_config() + main_config = markitect_config.__dict__ if hasattr(markitect_config, '__dict__') else {} + except Exception: + main_config = {} + if config_path is None: config_path = Path('.markitect/config/issues.yml') else: config_path = Path(config_path) - # Default configuration + # Extract configuration from main markitect config + gitea_url = main_config.get('gitea_url', 'http://92.205.130.254:32166') + api_token = main_config.get('api_token', '') + repo_owner = main_config.get('repo_owner', 'coulomb') + repo_name = main_config.get('repo_name', 'markitect_project') + + # Default configuration using main config values default_config = { 'default_backend': 'gitea', 'backends': { 'gitea': { - 'url': 'http://92.205.130.254:32166', - 'repo': 'coulomb/markitect_project' + 'url': gitea_url, + 'token': api_token, + 'repo_owner': repo_owner, + 'repo_name': repo_name, + 'repo': f'{repo_owner}/{repo_name}' }, 'local': { 'directory': '.markitect/issues', @@ -88,6 +107,17 @@ class IssuePluginManager: # Merge with defaults merged_config = default_config.copy() merged_config.update(config) + + # Ensure gitea backend has token from main config if not overridden + if 'backends' in merged_config and 'gitea' in merged_config['backends']: + gitea_config = merged_config['backends']['gitea'] + if not gitea_config.get('token'): + gitea_config['token'] = api_token + if not gitea_config.get('repo_owner'): + gitea_config['repo_owner'] = repo_owner + if not gitea_config.get('repo_name'): + gitea_config['repo_name'] = repo_name + return merged_config except Exception: # Return defaults if config loading fails diff --git a/markitect/issues/plugins/gitea.py b/markitect/issues/plugins/gitea.py index 6aeb049f..a489a6b5 100644 --- a/markitect/issues/plugins/gitea.py +++ b/markitect/issues/plugins/gitea.py @@ -20,6 +20,11 @@ class GiteaPlugin(IssueBackend): """Initialize Gitea plugin with configuration.""" super().__init__(config) + # Store repo info for API endpoints + self.repo_owner = config.get('repo_owner', 'coulomb') + self.repo_name = config.get('repo_name', 'markitect_project') + self.repo_full_name = f"{self.repo_owner}/{self.repo_name}" + # Create connection manager with configuration datasource_config = DataSourceConfig( gitea_base_url=config.get('url', 'http://92.205.130.254:32166'), @@ -28,7 +33,9 @@ class GiteaPlugin(IssueBackend): ) connection_manager = ConnectionManager(datasource_config) + # Create repository with repo info self.repository = GiteaIssueRepository(connection_manager) + self.repository.set_repo_info(self.repo_owner, self.repo_name) def list_issues(self, state: Optional[str] = None) -> List[Issue]: """List issues from Gitea."""