feat: refactor script template permissions — engineers manage own, add /share endpoint
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@ from sqlalchemy import select, func, or_
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.api.deps import get_current_active_user
|
||||
from app.core.permissions import can_manage_script_template, can_create_content
|
||||
from app.models.user import User
|
||||
from app.models.script_template import ScriptCategory, ScriptTemplate, ScriptGeneration
|
||||
from app.schemas.script_template import (
|
||||
@@ -27,14 +28,6 @@ router = APIRouter(prefix="/scripts", tags=["scripts"])
|
||||
_engine = ScriptTemplateEngine()
|
||||
|
||||
|
||||
def _require_team_admin(user: User) -> None:
|
||||
"""Raise 403 if user is not a team admin or super admin."""
|
||||
if not (user.is_team_admin or user.is_super_admin):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Team admin access required",
|
||||
)
|
||||
|
||||
|
||||
# ── Categories ────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -82,6 +75,7 @@ async def list_templates(
|
||||
category_slug: Optional[str] = Query(None),
|
||||
search: Optional[str] = Query(None),
|
||||
tags: Optional[str] = Query(None, description="Comma-separated tags"),
|
||||
managed: Optional[bool] = Query(None, description="If true, return only templates this user can edit"),
|
||||
) -> list[ScriptTemplateListItem]:
|
||||
query = (
|
||||
select(ScriptTemplate)
|
||||
@@ -108,6 +102,20 @@ async def list_templates(
|
||||
)
|
||||
)
|
||||
|
||||
if managed:
|
||||
if current_user.is_super_admin:
|
||||
pass # super admin can edit all
|
||||
elif current_user.account_role == "owner":
|
||||
query = query.where(
|
||||
or_(
|
||||
ScriptTemplate.created_by == current_user.id,
|
||||
ScriptTemplate.team_id != None, # noqa: E711
|
||||
)
|
||||
)
|
||||
else:
|
||||
# engineers see only their own
|
||||
query = query.where(ScriptTemplate.created_by == current_user.id)
|
||||
|
||||
result = await db.execute(query.order_by(ScriptTemplate.name))
|
||||
templates = result.scalars().all()
|
||||
|
||||
@@ -156,7 +164,11 @@ async def create_template(
|
||||
db: Annotated[AsyncSession, Depends(get_db)],
|
||||
current_user: Annotated[User, Depends(get_current_active_user)],
|
||||
) -> ScriptTemplateDetail:
|
||||
_require_team_admin(current_user)
|
||||
if not can_create_content(current_user):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Engineer access required to create templates",
|
||||
)
|
||||
|
||||
cat_result = await db.execute(
|
||||
select(ScriptCategory).where(
|
||||
@@ -202,19 +214,23 @@ async def update_template(
|
||||
db: Annotated[AsyncSession, Depends(get_db)],
|
||||
current_user: Annotated[User, Depends(get_current_active_user)],
|
||||
) -> ScriptTemplateDetail:
|
||||
_require_team_admin(current_user)
|
||||
|
||||
result = await db.execute(
|
||||
select(ScriptTemplate).where(
|
||||
ScriptTemplate.id == template_id,
|
||||
ScriptTemplate.team_id == current_user.team_id,
|
||||
ScriptTemplate.is_active == True, # noqa: E712
|
||||
)
|
||||
)
|
||||
template = result.scalar_one_or_none()
|
||||
if not template:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Template not found or not editable",
|
||||
detail="Template not found",
|
||||
)
|
||||
|
||||
if not can_manage_script_template(current_user, template.created_by, template.team_id):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="You don't have permission to edit this template",
|
||||
)
|
||||
|
||||
update_data = data.model_dump(exclude_unset=True)
|
||||
@@ -234,25 +250,66 @@ async def delete_template(
|
||||
db: Annotated[AsyncSession, Depends(get_db)],
|
||||
current_user: Annotated[User, Depends(get_current_active_user)],
|
||||
) -> None:
|
||||
_require_team_admin(current_user)
|
||||
|
||||
result = await db.execute(
|
||||
select(ScriptTemplate).where(
|
||||
ScriptTemplate.id == template_id,
|
||||
ScriptTemplate.team_id == current_user.team_id,
|
||||
ScriptTemplate.is_active == True, # noqa: E712
|
||||
)
|
||||
)
|
||||
template = result.scalar_one_or_none()
|
||||
if not template:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Template not found or not deletable",
|
||||
detail="Template not found",
|
||||
)
|
||||
|
||||
if not can_manage_script_template(current_user, template.created_by, template.team_id):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="You don't have permission to delete this template",
|
||||
)
|
||||
|
||||
template.is_active = False
|
||||
await db.commit()
|
||||
|
||||
|
||||
@router.patch("/templates/{template_id}/share", response_model=ScriptTemplateDetail)
|
||||
async def share_template(
|
||||
template_id: UUID,
|
||||
db: Annotated[AsyncSession, Depends(get_db)],
|
||||
current_user: Annotated[User, Depends(get_current_active_user)],
|
||||
shared: bool = Query(..., description="true to share with team, false to make personal"),
|
||||
) -> ScriptTemplateDetail:
|
||||
"""Toggle team sharing for a template. Owner/admin/super_admin only."""
|
||||
if not (current_user.is_super_admin or current_user.account_role == "owner"):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Only account owners and admins can share templates",
|
||||
)
|
||||
|
||||
result = await db.execute(
|
||||
select(ScriptTemplate).where(
|
||||
ScriptTemplate.id == template_id,
|
||||
ScriptTemplate.is_active == True, # noqa: E712
|
||||
)
|
||||
)
|
||||
template = result.scalar_one_or_none()
|
||||
if not template:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Template not found",
|
||||
)
|
||||
|
||||
if shared:
|
||||
template.team_id = current_user.team_id
|
||||
else:
|
||||
template.team_id = None
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(template)
|
||||
return ScriptTemplateDetail.model_validate(template)
|
||||
|
||||
|
||||
# ── Generate ──────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user