gabriel / musehub public
bridge_service.py python
104 lines 3.3 KB
Raw
sha256:c73281cffffbc70487969720af074e54b34326d59ec6aff0827fa16512a4e512 docs(mwp-2): mark all acceptance criteria met; add closing … Sonnet 4.6 82 days ago
1 """Bridge mirror service — persistence adapter for musehub_bridge_mirrors.
2
3 This module is the sole point of DB access for the bridge mirror registry.
4 Route handlers delegate here; no business logic lives in routes.
5
6 Boundary rules:
7 - Must NOT import state stores, SSE queues, or LLM clients.
8 - May import ORM models from musehub.db domain-specific modules.
9 - May import Pydantic models from musehub.models.bridge.
10 """
11
12 import logging
13 from datetime import datetime, timezone
14
15 from sqlalchemy import select
16 from sqlalchemy.exc import IntegrityError
17 from sqlalchemy.ext.asyncio import AsyncSession
18
19 from musehub.core.genesis import compute_bridge_mirror_id
20 from musehub.db.musehub_repo_models import MusehubBridgeMirror
21 from musehub.models.bridge import CreateMirrorRequest
22
23 logger = logging.getLogger(__name__)
24
25 def _utc_now() -> datetime:
26 return datetime.now(tz=timezone.utc)
27
28 async def create_mirror(
29 session: AsyncSession,
30 repo_id: str,
31 request: CreateMirrorRequest,
32 created_by: str,
33 ) -> MusehubBridgeMirror:
34 """Create a new bridge mirror registration for a repo.
35
36 Raises ``IntegrityError`` (409 at the route layer) when the same
37 ``git_remote_url`` is already registered for this ``repo_id``.
38 """
39 mirror = MusehubBridgeMirror(
40 id=compute_bridge_mirror_id(repo_id, request.git_remote_url),
41 repo_id=repo_id,
42 git_remote_url=request.git_remote_url,
43 git_branch=request.git_branch,
44 direction=request.direction,
45 auto_export=request.auto_export,
46 created_by=created_by,
47 )
48 session.add(mirror)
49 # Flush here so we propagate IntegrityError before the caller commits.
50 await session.flush()
51 return mirror
52
53 async def list_mirrors(
54 session: AsyncSession,
55 repo_id: str,
56 ) -> list[MusehubBridgeMirror]:
57 """Return all bridge mirrors registered for *repo_id*, ordered by creation time."""
58 result = await session.execute(
59 select(MusehubBridgeMirror)
60 .where(MusehubBridgeMirror.repo_id == repo_id)
61 .order_by(MusehubBridgeMirror.created_at)
62 )
63 return list(result.scalars().all())
64
65 async def delete_mirror(
66 session: AsyncSession,
67 repo_id: str,
68 mirror_id: str,
69 ) -> bool:
70 """Delete the mirror with *mirror_id* that belongs to *repo_id*.
71
72 Returns ``True`` when the row was deleted; ``False`` when it was not found.
73 """
74 result = await session.execute(
75 select(MusehubBridgeMirror).where(
76 MusehubBridgeMirror.id == mirror_id,
77 MusehubBridgeMirror.repo_id == repo_id,
78 )
79 )
80 mirror = result.scalar_one_or_none()
81 if mirror is None:
82 return False
83 await session.delete(mirror)
84 await session.flush()
85 return True
86
87 async def update_mirror_export_state(
88 session: AsyncSession,
89 mirror_id: str,
90 muse_commit_id: str,
91 git_sha: str,
92 ) -> None:
93 """Record a successful export operation on the given mirror."""
94 result = await session.execute(
95 select(MusehubBridgeMirror).where(MusehubBridgeMirror.id == mirror_id)
96 )
97 mirror = result.scalar_one_or_none()
98 if mirror is None:
99 logger.warning("update_mirror_export_state: mirror %s not found", mirror_id)
100 return
101 mirror.last_export_muse_commit_id = muse_commit_id
102 mirror.last_export_git_sha = git_sha
103 mirror.last_export_at = _utc_now()
104 await session.flush()
File History 2 commits
sha256:c73281cffffbc70487969720af074e54b34326d59ec6aff0827fa16512a4e512 docs(mwp-2): mark all acceptance criteria met; add closing … Sonnet 4.6 82 days ago
sha256:4dd2a937f66f8e36d9aa59bd1bf3cb880ca1d503fef67300a0cba3b482784081 test(mwp2): Phase 0 RED — reproduction tests for _walk_comm… Sonnet 4.6 82 days ago