Spaces:
Running
Running
Ashraf Al-Kassem Claude Opus 4.6 commited on
Commit ·
721b3fc
1
Parent(s): 38213a5
fix: eliminate 307 redirects that break login and API calls behind HF proxy
Browse filesThe previous trailing-slash normalization in api.ts inadvertently broke
login (POST /auth/login/ → 307 → /auth/login, losing FormData).
Root cause: FastAPI routes defined as @router .get("/") only match paths
WITH trailing slash, causing 307 redirects for requests WITHOUT one.
Behind HuggingFace's reverse proxy, these redirects silently fail.
Fix: Change route definitions from "/" to "" so they match the exact
paths the frontend sends (no trailing slash). Revert the frontend
trailing-slash hack.
Routes fixed: prompt_config (GET, POST), integrations (GET),
workspaces (GET, POST), automations (GET).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
backend/app/api/v1/automations.py
CHANGED
|
@@ -30,7 +30,7 @@ class BuilderPayload(BaseModel):
|
|
| 30 |
steps: List[BuilderStep]
|
| 31 |
publish: bool = False
|
| 32 |
|
| 33 |
-
@router.get("
|
| 34 |
async def list_flows(
|
| 35 |
db: AsyncSession = Depends(get_db),
|
| 36 |
current_user: User = Depends(deps.get_current_user)
|
|
|
|
| 30 |
steps: List[BuilderStep]
|
| 31 |
publish: bool = False
|
| 32 |
|
| 33 |
+
@router.get("")
|
| 34 |
async def list_flows(
|
| 35 |
db: AsyncSession = Depends(get_db),
|
| 36 |
current_user: User = Depends(deps.get_current_user)
|
backend/app/api/v1/integrations.py
CHANGED
|
@@ -17,7 +17,7 @@ from app.core.modules import require_module_enabled, MODULE_INTEGRATIONS_CONNECT
|
|
| 17 |
|
| 18 |
router = APIRouter()
|
| 19 |
|
| 20 |
-
@router.get("
|
| 21 |
async def list_integrations(
|
| 22 |
db: AsyncSession = Depends(get_db),
|
| 23 |
workspace: Workspace = Depends(deps.get_active_workspace),
|
|
|
|
| 17 |
|
| 18 |
router = APIRouter()
|
| 19 |
|
| 20 |
+
@router.get("", response_model=ResponseEnvelope[List[IntegrationRead]], dependencies=[Depends(require_module_enabled(MODULE_INTEGRATIONS_CONNECT, "read"))])
|
| 21 |
async def list_integrations(
|
| 22 |
db: AsyncSession = Depends(get_db),
|
| 23 |
workspace: Workspace = Depends(deps.get_active_workspace),
|
backend/app/api/v1/prompt_config.py
CHANGED
|
@@ -50,7 +50,7 @@ def compile_prompt(data: Dict[str, Any]) -> str:
|
|
| 50 |
|
| 51 |
return "\n".join(lines)
|
| 52 |
|
| 53 |
-
@router.get("
|
| 54 |
async def get_prompt_config(
|
| 55 |
db: AsyncSession = Depends(get_db),
|
| 56 |
workspace: Workspace = Depends(deps.get_active_workspace),
|
|
@@ -87,7 +87,7 @@ async def get_prompt_config(
|
|
| 87 |
import logging
|
| 88 |
logger = logging.getLogger(__name__)
|
| 89 |
|
| 90 |
-
@router.post("
|
| 91 |
async def create_prompt_version(
|
| 92 |
*,
|
| 93 |
db: AsyncSession = Depends(get_db),
|
|
|
|
| 50 |
|
| 51 |
return "\n".join(lines)
|
| 52 |
|
| 53 |
+
@router.get("", response_model=ResponseEnvelope[PromptConfigRead], dependencies=[Depends(require_module_enabled(MODULE_PROMPT_STUDIO, "read"))])
|
| 54 |
async def get_prompt_config(
|
| 55 |
db: AsyncSession = Depends(get_db),
|
| 56 |
workspace: Workspace = Depends(deps.get_active_workspace),
|
|
|
|
| 87 |
import logging
|
| 88 |
logger = logging.getLogger(__name__)
|
| 89 |
|
| 90 |
+
@router.post("", response_model=ResponseEnvelope[PromptVersionRead], dependencies=[Depends(require_module_enabled(MODULE_PROMPT_STUDIO, "write"))])
|
| 91 |
async def create_prompt_version(
|
| 92 |
*,
|
| 93 |
db: AsyncSession = Depends(get_db),
|
backend/app/api/v1/workspaces.py
CHANGED
|
@@ -14,7 +14,7 @@ from app.schemas.envelope import ResponseEnvelope, wrap_data, wrap_error
|
|
| 14 |
|
| 15 |
router = APIRouter()
|
| 16 |
|
| 17 |
-
@router.post("
|
| 18 |
async def create_workspace(
|
| 19 |
*,
|
| 20 |
db: AsyncSession = Depends(get_db),
|
|
@@ -36,7 +36,7 @@ async def create_workspace(
|
|
| 36 |
await db.refresh(workspace)
|
| 37 |
return wrap_data(workspace)
|
| 38 |
|
| 39 |
-
@router.get("
|
| 40 |
async def list_workspaces(
|
| 41 |
db: AsyncSession = Depends(get_db),
|
| 42 |
current_user: User = Depends(deps.get_current_user)
|
|
|
|
| 14 |
|
| 15 |
router = APIRouter()
|
| 16 |
|
| 17 |
+
@router.post("", response_model=ResponseEnvelope[WorkspaceRead])
|
| 18 |
async def create_workspace(
|
| 19 |
*,
|
| 20 |
db: AsyncSession = Depends(get_db),
|
|
|
|
| 36 |
await db.refresh(workspace)
|
| 37 |
return wrap_data(workspace)
|
| 38 |
|
| 39 |
+
@router.get("", response_model=ResponseEnvelope[List[WorkspaceRead]])
|
| 40 |
async def list_workspaces(
|
| 41 |
db: AsyncSession = Depends(get_db),
|
| 42 |
current_user: User = Depends(deps.get_current_user)
|
frontend/src/lib/admin-api.ts
CHANGED
|
@@ -27,9 +27,7 @@ async function adminRequest<T = any>(
|
|
| 27 |
): Promise<ApiResponse<T>> {
|
| 28 |
const base = getBaseUrl().replace(/\/$/, "");
|
| 29 |
const cleanPath = path.startsWith("/") ? path : `/${path}`;
|
| 30 |
-
|
| 31 |
-
const normalizedPath = cleanPath.endsWith("/") ? cleanPath : `${cleanPath}/`;
|
| 32 |
-
const url = `${base}/api/v1${normalizedPath}`;
|
| 33 |
|
| 34 |
const token = adminAuth.getToken();
|
| 35 |
const headers = new Headers(options.headers || {});
|
|
|
|
| 27 |
): Promise<ApiResponse<T>> {
|
| 28 |
const base = getBaseUrl().replace(/\/$/, "");
|
| 29 |
const cleanPath = path.startsWith("/") ? path : `/${path}`;
|
| 30 |
+
const url = `${base}/api/v1${cleanPath}`;
|
|
|
|
|
|
|
| 31 |
|
| 32 |
const token = adminAuth.getToken();
|
| 33 |
const headers = new Headers(options.headers || {});
|
frontend/src/lib/api.ts
CHANGED
|
@@ -50,10 +50,7 @@ class ApiClient {
|
|
| 50 |
// Safe base URL with /api/v1 prefix - ALWAYS absolute
|
| 51 |
const base = API_BASE_URL.replace(/\/$/, "");
|
| 52 |
const cleanPath = path.startsWith("/") ? path : `/${path}`;
|
| 53 |
-
|
| 54 |
-
// behind reverse proxies (e.g. HuggingFace Spaces)
|
| 55 |
-
const normalizedPath = cleanPath.endsWith("/") ? cleanPath : `${cleanPath}/`;
|
| 56 |
-
const url = `${base}/api/v1${normalizedPath}`;
|
| 57 |
|
| 58 |
const method = options.method || "GET";
|
| 59 |
console.log(`[API] ${method} ${url}`);
|
|
|
|
| 50 |
// Safe base URL with /api/v1 prefix - ALWAYS absolute
|
| 51 |
const base = API_BASE_URL.replace(/\/$/, "");
|
| 52 |
const cleanPath = path.startsWith("/") ? path : `/${path}`;
|
| 53 |
+
const url = `${base}/api/v1${cleanPath}`;
|
|
|
|
|
|
|
|
|
|
| 54 |
|
| 55 |
const method = options.method || "GET";
|
| 56 |
console.log(`[API] ${method} ${url}`);
|