gabriel / musehub public
social.py python
144 lines 4.8 KB
e994d391 feat(mcp): best-in-class MCP 2025-03-26 integration — 27 tools, 20 reso… Gabriel Cardona <gabriel@tellurstori.com> 6d ago
1 """Write executors for social operations: star_repo, create_label."""
2 from __future__ import annotations
3
4 import logging
5 import uuid
6
7 from musehub.contracts.json_types import JSONValue
8 from musehub.db.database import AsyncSessionLocal
9 from musehub.services import musehub_discover, musehub_repository
10 from musehub.services.musehub_mcp_executor import MusehubToolResult, _check_db_available
11
12 logger = logging.getLogger(__name__)
13
14
15 async def execute_star_repo(
16 *,
17 repo_id: str,
18 actor: str,
19 ) -> MusehubToolResult:
20 """Star a MuseHub repository on behalf of the authenticated user.
21
22 Idempotent: starring a repo that is already starred returns success.
23
24 Args:
25 repo_id: UUID of the repository to star.
26 actor: Authenticated user ID (JWT ``sub`` claim).
27
28 Returns:
29 ``MusehubToolResult`` with ``data.starred`` on success.
30 """
31 if (err := _check_db_available()) is not None:
32 return err
33
34 try:
35 async with AsyncSessionLocal() as session:
36 repo = await musehub_repository.get_repo(session, repo_id)
37 if repo is None:
38 return MusehubToolResult(
39 ok=False,
40 error_code="not_found",
41 error_message=f"Repository '{repo_id}' not found.",
42 )
43 result = await musehub_discover.star_repo(session, repo_id, actor)
44 await session.commit()
45 data: dict[str, JSONValue] = {
46 "repo_id": repo_id,
47 "starred": True,
48 "star_count": result.star_count,
49 }
50 logger.info("MCP star_repo %s by %s", repo_id, actor)
51 return MusehubToolResult(ok=True, data=data)
52 except Exception as exc:
53 logger.exception("MCP star_repo failed: %s", exc)
54 return MusehubToolResult(
55 ok=False,
56 error_code="not_found",
57 error_message=str(exc),
58 )
59
60
61 async def execute_create_label(
62 *,
63 repo_id: str,
64 name: str,
65 color: str,
66 description: str = "",
67 actor: str = "",
68 ) -> MusehubToolResult:
69 """Create a repo-scoped label with a name and hex colour.
70
71 Label names must be unique within the repository.
72
73 Args:
74 repo_id: UUID of the repository.
75 name: Label name (must be unique per repo).
76 color: 6-char hex colour string without ``#`` prefix (e.g. ``"e11d48"``).
77 description: Optional label description.
78 actor: Authenticated user ID (JWT ``sub`` claim).
79
80 Returns:
81 ``MusehubToolResult`` with ``data.label_id`` on success.
82 """
83 if (err := _check_db_available()) is not None:
84 return err
85
86 try:
87 from sqlalchemy import text # local import
88
89 async with AsyncSessionLocal() as session:
90 repo = await musehub_repository.get_repo(session, repo_id)
91 if repo is None:
92 return MusehubToolResult(
93 ok=False,
94 error_code="not_found",
95 error_message=f"Repository '{repo_id}' not found.",
96 )
97
98 # Check name uniqueness.
99 existing = await session.execute(
100 text(
101 "SELECT 1 FROM musehub_labels "
102 "WHERE repo_id = :repo_id AND name = :name"
103 ),
104 {"repo_id": repo_id, "name": name},
105 )
106 if existing.scalar_one_or_none() is not None:
107 return MusehubToolResult(
108 ok=False,
109 error_code="not_found",
110 error_message=f"Label '{name}' already exists in repo '{repo_id}'.",
111 )
112
113 label_id = str(uuid.uuid4())
114 await session.execute(
115 text(
116 "INSERT INTO musehub_labels "
117 "(id, repo_id, name, color, description, created_at) "
118 "VALUES (:label_id, :repo_id, :name, :color, :description, CURRENT_TIMESTAMP)"
119 ),
120 {
121 "label_id": label_id,
122 "repo_id": repo_id,
123 "name": name,
124 "color": color,
125 "description": description,
126 },
127 )
128 await session.commit()
129 data: dict[str, JSONValue] = {
130 "label_id": label_id,
131 "repo_id": repo_id,
132 "name": name,
133 "color": color,
134 "description": description,
135 }
136 logger.info("MCP create_label '%s' (%s) in repo %s", name, label_id, repo_id)
137 return MusehubToolResult(ok=True, data=data)
138 except Exception as exc:
139 logger.exception("MCP create_label failed: %s", exc)
140 return MusehubToolResult(
141 ok=False,
142 error_code="not_found",
143 error_message=str(exc),
144 )