mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-07-19 11:41:04 +02:00
Initial Commit 🚀 🚀
This commit is contained in:
commit
4f2a629340
444 changed files with 76863 additions and 0 deletions
185
api/routes/superuser.py
Normal file
185
api/routes/superuser.py
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
import json
|
||||
from datetime import datetime
|
||||
from typing import List, Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from pydantic import BaseModel
|
||||
|
||||
from api.db import db_client
|
||||
from api.db.models import UserModel
|
||||
from api.services.auth.depends import get_superuser
|
||||
from api.services.auth.stack_auth import stackauth
|
||||
|
||||
router = APIRouter(prefix="/superuser", tags=["superuser"])
|
||||
|
||||
|
||||
class ImpersonateRequest(BaseModel):
|
||||
"""Request payload for superadmin impersonation.
|
||||
|
||||
Either ``provider_user_id`` **or** ``user_id`` must be supplied. If both are
|
||||
provided, ``provider_user_id`` takes precedence.
|
||||
"""
|
||||
|
||||
provider_user_id: str | None = None
|
||||
user_id: int | None = None
|
||||
|
||||
|
||||
class ImpersonateResponse(BaseModel):
|
||||
refresh_token: str
|
||||
access_token: str
|
||||
|
||||
|
||||
class SuperuserWorkflowRunResponse(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
workflow_id: int
|
||||
workflow_name: Optional[str]
|
||||
user_id: Optional[int]
|
||||
organization_id: Optional[int]
|
||||
organization_name: Optional[str]
|
||||
mode: str
|
||||
is_completed: bool
|
||||
recording_url: Optional[str]
|
||||
transcript_url: Optional[str]
|
||||
usage_info: Optional[dict]
|
||||
cost_info: Optional[dict]
|
||||
initial_context: Optional[dict]
|
||||
gathered_context: Optional[dict]
|
||||
admin_comment: Optional[str]
|
||||
admin_comment_ts: Optional[datetime]
|
||||
created_at: datetime
|
||||
|
||||
|
||||
class SuperuserWorkflowRunsListResponse(BaseModel):
|
||||
workflow_runs: List[SuperuserWorkflowRunResponse]
|
||||
total_count: int
|
||||
page: int
|
||||
limit: int
|
||||
total_pages: int
|
||||
|
||||
|
||||
@router.post("/impersonate")
|
||||
async def impersonate(
|
||||
request: ImpersonateRequest, user: UserModel = Depends(get_superuser)
|
||||
) -> ImpersonateResponse:
|
||||
"""Impersonate a user as a super-admin.
|
||||
Internally, Stack Auth requires the **provider user ID** (a UUID-ish string)
|
||||
to create an impersonation session.
|
||||
"""
|
||||
|
||||
provider_user_id: str | None = request.provider_user_id
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Fallback: resolve provider_user_id from internal ``user_id``
|
||||
# ------------------------------------------------------------------
|
||||
if provider_user_id is None:
|
||||
if request.user_id is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Either 'provider_user_id' or 'user_id' must be provided.",
|
||||
)
|
||||
|
||||
db_user = await db_client.get_user_by_id(request.user_id)
|
||||
|
||||
if db_user is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"User with ID {request.user_id} not found.",
|
||||
)
|
||||
|
||||
provider_user_id = db_user.provider_id
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Call Stack Auth to create the impersonation session
|
||||
# ------------------------------------------------------------------
|
||||
session = await stackauth.impersonate(provider_user_id)
|
||||
|
||||
return ImpersonateResponse(
|
||||
refresh_token=session["refresh_token"],
|
||||
access_token=session["access_token"],
|
||||
)
|
||||
|
||||
|
||||
@router.get("/workflow-runs")
|
||||
async def get_workflow_runs(
|
||||
page: int = Query(1, ge=1, description="Page number (starts from 1)"),
|
||||
limit: int = Query(50, ge=1, le=100, description="Number of items per page"),
|
||||
filters: Optional[str] = Query(None, description="JSON-encoded filter criteria"),
|
||||
user: UserModel = Depends(get_superuser),
|
||||
) -> SuperuserWorkflowRunsListResponse:
|
||||
"""
|
||||
Get paginated list of all workflow runs with organization information.
|
||||
Requires superuser privileges.
|
||||
|
||||
Filters should be provided as a JSON-encoded array of filter criteria.
|
||||
Example: [{"field": "id", "type": "number", "value": {"value": 680}}]
|
||||
"""
|
||||
offset = (page - 1) * limit
|
||||
|
||||
# Parse filters if provided
|
||||
filter_criteria = None
|
||||
if filters:
|
||||
try:
|
||||
filter_criteria = json.loads(filters)
|
||||
except json.JSONDecodeError:
|
||||
raise HTTPException(status_code=400, detail="Invalid filter format")
|
||||
|
||||
workflow_runs, total_count = await db_client.get_workflow_runs_for_superadmin(
|
||||
limit=limit, offset=offset, filters=filter_criteria
|
||||
)
|
||||
|
||||
total_pages = (total_count + limit - 1) // limit # Ceiling division
|
||||
|
||||
return SuperuserWorkflowRunsListResponse(
|
||||
workflow_runs=[SuperuserWorkflowRunResponse(**run) for run in workflow_runs],
|
||||
total_count=total_count,
|
||||
page=page,
|
||||
limit=limit,
|
||||
total_pages=total_pages,
|
||||
)
|
||||
|
||||
|
||||
# ------------------ Admin Comment ------------------
|
||||
|
||||
|
||||
class AdminCommentRequest(BaseModel):
|
||||
admin_comment: str
|
||||
|
||||
|
||||
class AdminCommentResponse(BaseModel):
|
||||
success: bool
|
||||
admin_comment: str
|
||||
admin_comment_ts: datetime
|
||||
|
||||
|
||||
# ------------------ Routes ------------------
|
||||
|
||||
|
||||
@router.post("/workflow-runs/{run_id}/comment", response_model=AdminCommentResponse)
|
||||
async def set_admin_comment(
|
||||
run_id: int,
|
||||
request: AdminCommentRequest,
|
||||
user: UserModel = Depends(get_superuser),
|
||||
):
|
||||
"""Add or update an *admin-only* comment for a workflow run.
|
||||
|
||||
The comment is stored inside the ``annotations`` JSON column under the
|
||||
``admin_comment`` key so that it does not interfere with any other
|
||||
annotations recorded by the system.
|
||||
"""
|
||||
|
||||
await db_client.update_admin_comment(
|
||||
run_id=run_id, admin_comment=request.admin_comment
|
||||
)
|
||||
|
||||
# Fetch the updated run to get the timestamp from annotations
|
||||
updated_run = await db_client.get_workflow_run_by_id(run_id)
|
||||
admin_comment_ts = None
|
||||
if updated_run and updated_run.annotations:
|
||||
admin_comment_ts = updated_run.annotations.get("admin_comment_ts")
|
||||
|
||||
return AdminCommentResponse(
|
||||
success=True,
|
||||
admin_comment=request.admin_comment,
|
||||
admin_comment_ts=admin_comment_ts,
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue