"""Authorized, versioned navigation contract for the single SiteForge console. The frontend is not an authorization authority: every business API keeps its own server-side membership checks. This endpoint only returns allowed context. """ from fastapi import APIRouter, HTTPException, Request from . import db, enterprise from .version import VERSION router = APIRouter(prefix='/api/console', tags=['Console']) NAVIGATION = ( ('dashboard', '工作台', '/siteforge/dashboard', None), ('sites', '网站管理', '/siteforge/factory', None), ('content', '内容管理', '/siteforge/content', 'viewer'), ('design', '网站设计', '/siteforge/editor', 'viewer'), ('migration', '数据与迁移', '/siteforge/migration', 'viewer'), ('release', '构建与发布', '/siteforge/releases', 'viewer'), ('system', '系统管理', '/siteforge/enterprise', 'owner'), ) def available_sites(request: Request): if request.state.is_superadmin: return db.rows('SELECT id,name,domain FROM sites ORDER BY updated_at DESC,id') return db.rows('''SELECT s.id,s.name,s.domain FROM sites s JOIN team_memberships m ON m.site_id=s.id JOIN team_users u ON u.id=m.user_id WHERE m.user_id=? AND u.disabled=0 ORDER BY s.updated_at DESC,s.id''', (request.state.account_id,)) def capabilities(request: Request, site_id: str | None): role = 'superadmin' if request.state.is_superadmin else 'none' if site_id and not request.state.is_superadmin: membership = db.one('SELECT role FROM team_memberships WHERE site_id=? AND user_id=?', (site_id, request.state.account_id)) role = membership['role'] if membership else 'none' level = enterprise.ACCESS.get(role, -1) return dict(role=role, view=level>=0, edit=level>=enterprise.ACCESS['editor'], review=level>=enterprise.ACCESS['reviewer'], deploy=level>=enterprise.ACCESS['owner'], manage_members=level>=enterprise.ACCESS['owner'], create_site=bool(request.state.is_superadmin), manage_users=bool(request.state.is_superadmin)) @router.get('/bootstrap') def bootstrap(request: Request, site_id: str = ''): """Return user-scoped menus, never expose unassigned websites or credentials.""" sites = available_sites(request) ids = {x['id'] for x in sites} if site_id and site_id not in ids: raise HTTPException(403, '没有访问该网站的权限') selected = site_id or (sites[0]['id'] if sites else None) rights = capabilities(request, selected) sections = [] for key, title, path, required in NAVIGATION: if required and not selected: continue if required and not rights['view']: continue if key == 'system' and not (rights['manage_members'] or rights['manage_users']): continue sections.append({'key': key, 'title': title, 'path': path, 'requires_site': bool(required)}) return { 'version': VERSION, 'user': request.state.actor, 'superadmin': bool(request.state.is_superadmin), 'sites': sites, 'selected_site_id': selected, 'capabilities': rights, 'navigation': sections, }