gabriel / musehub public
ui_repo_settings.py python
137 lines 5.2 KB
Raw
sha256:a34090cc4a394a78bd72cbbe34b08cc59525141e19135b6c0ab154f10611b9ef debug(push/stream): instrument O-frame decode path with INF… Sonnet 4.6 patch 121 days ago
1 """MuseHub repo settings and collaborator management UI routes.
2
3 Endpoints:
4 GET /{owner}/{repo_slug}/settings — repo settings page
5 GET /{owner}/{repo_slug}/settings/collaborators — team management page
6 """
7
8 import logging
9
10 from fastapi import APIRouter, Depends, HTTPException, Query, Request
11 from fastapi import status as http_status
12 from sqlalchemy import select
13 from sqlalchemy.ext.asyncio import AsyncSession
14 from starlette.responses import Response
15
16 from musehub.api.routes.musehub._templates import templates
17 from musehub.api.validation import BranchParam, FilePathParam, SlugParam
18 from musehub.api.routes.musehub._ui_helpers import _resolve_repo
19 from musehub.api.routes.musehub.collaborators import CollaboratorListResponse, _orm_to_response
20 from musehub.api.routes.musehub.negotiate import negotiate_response
21 from musehub.db import get_db
22 from musehub.db.musehub_collaborator_models import MusehubCollaborator
23 from musehub.models.musehub import RepoSettingsResponse
24 from musehub.services import musehub_repository
25 from musehub.types.json_types import JSONObject
26
27 logger = logging.getLogger(__name__)
28
29 router = APIRouter(prefix="", tags=["musehub-ui-settings"])
30
31 @router.get(
32 "/{owner}/{repo_slug}/settings",
33 summary="MuseHub repo settings page",
34 )
35 async def settings_page(
36 request: Request,
37 owner: SlugParam,
38 repo_slug: SlugParam,
39 section: str = Query(
40 "general",
41 pattern="^(general|collaboration|merge|danger)$",
42 description="Settings sidebar section to show on load",
43 ),
44 format: str | None = Query(None, description="Force response format: 'json' or omit for HTML"),
45 db: AsyncSession = Depends(get_db),
46 ) -> Response:
47 """Render the repo settings page or return current settings as JSON.
48
49 HTML sections (sidebar navigation):
50 - ``general`` — Basic identity: name, description, URL, topics, license, visibility.
51 - ``collaboration`` — Invite/remove collaborators; set per-collaborator permissions.
52 - ``merge`` — Enable/disable merge commit, squash, and rebase merge strategies;
53 toggle auto-delete of head branch after merge.
54 - ``danger`` — Archive repo (read-only), transfer to another owner,
55 delete repo (requires typing the full repo name to confirm).
56
57 No auth required to render the HTML shell. All write operations require
58 a valid owner/admin MSign token, enforced by the API layer.
59 """
60 repo_id, base_url, _ = await _resolve_repo(owner, repo_slug, db)
61 settings: RepoSettingsResponse | None = await musehub_repository.get_repo_settings(
62 db, repo_id
63 )
64 return await negotiate_response(
65 request=request,
66 template_name="musehub/pages/settings.html",
67 context={
68 "owner": owner,
69 "repo_slug": repo_slug,
70 "repo_id": repo_id,
71 "base_url": base_url,
72 "active_section": section,
73 "current_page": "settings",
74 "settings": settings,
75 "breadcrumb_data": [
76 {"label": owner, "url": f"/{owner}"},
77 {"label": repo_slug, "url": base_url},
78 {"label": "Settings", "url": ""},
79 ],
80 },
81 templates=templates,
82 json_data=settings,
83 format_param=format,
84 )
85
86 @router.get(
87 "/{owner}/{repo_slug}/settings/collaborators",
88 summary="MuseHub team management page — add/remove collaborators and set permissions",
89 )
90 async def collaborators_settings_page(
91 request: Request,
92 owner: SlugParam,
93 repo_slug: SlugParam,
94 format: str | None = Query(None, description="Force response format: 'json' or omit for HTML"),
95 db: AsyncSession = Depends(get_db),
96 ) -> Response:
97 """Render the SSR collaborators/team management page.
98
99 HTML (default): renders ``musehub/pages/collaborators_settings.html``.
100 HTMX fragment (``HX-Request: true``): returns only the collaborator list fragment.
101 JSON (``Accept: application/json`` or ``?format=json``): returns ``CollaboratorListResponse``.
102 """
103 repo_id, base_url, _ = await _resolve_repo(owner, repo_slug, db)
104
105 result = await db.execute(
106 select(MusehubCollaborator).where(MusehubCollaborator.repo_id == repo_id)
107 )
108 rows = result.scalars().all()
109 collaborator_responses = [_orm_to_response(r) for r in rows]
110 json_data = CollaboratorListResponse(
111 collaborators=collaborator_responses, total=len(collaborator_responses)
112 )
113
114 ctx: JSONObject = {
115 "owner": owner,
116 "repo_slug": repo_slug,
117 "repo_id": repo_id,
118 "base_url": base_url,
119 "current_page": "settings",
120 "settings_tab": "collaborators",
121 "collaborators": rows,
122 "breadcrumb_data": [
123 {"label": owner, "url": f"/{owner}"},
124 {"label": repo_slug, "url": base_url},
125 {"label": "Settings", "url": f"{base_url}/settings"},
126 {"label": "Collaborators", "url": ""},
127 ],
128 }
129 return await negotiate_response(
130 request=request,
131 template_name="musehub/pages/collaborators_settings.html",
132 context=ctx,
133 templates=templates,
134 json_data=json_data,
135 format_param=format,
136 fragment_template="musehub/fragments/collaborator_rows.html",
137 )
File History 1 commit
sha256:a34090cc4a394a78bd72cbbe34b08cc59525141e19135b6c0ab154f10611b9ef debug(push/stream): instrument O-frame decode path with INF… Sonnet 4.6 patch 121 days ago