mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-02 22:01:05 +02:00
refactor(web): replace BACKEND_URL with buildBackendUrl for dynamic URL construction
This commit is contained in:
parent
66659ee8d3
commit
371ff866c7
9 changed files with 138 additions and 79 deletions
|
|
@ -106,7 +106,7 @@ import {
|
||||||
extractUserTurnForNewChatApi,
|
extractUserTurnForNewChatApi,
|
||||||
type NewChatUserImagePayload,
|
type NewChatUserImagePayload,
|
||||||
} from "@/lib/chat/user-turn-api-parts";
|
} from "@/lib/chat/user-turn-api-parts";
|
||||||
import { BACKEND_URL } from "@/lib/env-config";
|
import { buildBackendUrl } from "@/lib/env-config";
|
||||||
import { NotFoundError } from "@/lib/error";
|
import { NotFoundError } from "@/lib/error";
|
||||||
import {
|
import {
|
||||||
trackChatBlocked,
|
trackChatBlocked,
|
||||||
|
|
@ -919,10 +919,9 @@ export default function NewChatPage() {
|
||||||
if (threadId) {
|
if (threadId) {
|
||||||
const token = getBearerToken();
|
const token = getBearerToken();
|
||||||
if (token) {
|
if (token) {
|
||||||
const backendUrl = BACKEND_URL;
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${backendUrl}/api/v1/threads/${threadId}/cancel-active-turn`,
|
buildBackendUrl(`/api/v1/threads/${threadId}/cancel-active-turn`),
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
|
|
@ -1110,7 +1109,6 @@ export default function NewChatPage() {
|
||||||
let streamBatcher: FrameBatchedUpdater | null = null;
|
let streamBatcher: FrameBatchedUpdater | null = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const backendUrl = BACKEND_URL;
|
|
||||||
const selection = await getAgentFilesystemSelection(searchSpaceId, {
|
const selection = await getAgentFilesystemSelection(searchSpaceId, {
|
||||||
localFilesystemEnabled,
|
localFilesystemEnabled,
|
||||||
});
|
});
|
||||||
|
|
@ -1147,7 +1145,7 @@ export default function NewChatPage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetchWithTurnCancellingRetry(() =>
|
const response = await fetchWithTurnCancellingRetry(() =>
|
||||||
fetch(`${backendUrl}/api/v1/new_chat`, {
|
fetch(buildBackendUrl("/api/v1/new_chat"), {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
|
@ -1642,12 +1640,11 @@ export default function NewChatPage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const backendUrl = BACKEND_URL;
|
|
||||||
const selection = await getAgentFilesystemSelection(searchSpaceId, {
|
const selection = await getAgentFilesystemSelection(searchSpaceId, {
|
||||||
localFilesystemEnabled,
|
localFilesystemEnabled,
|
||||||
});
|
});
|
||||||
const response = await fetchWithTurnCancellingRetry(() =>
|
const response = await fetchWithTurnCancellingRetry(() =>
|
||||||
fetch(`${backendUrl}/api/v1/threads/${resumeThreadId}/resume`, {
|
fetch(buildBackendUrl(`/api/v1/threads/${resumeThreadId}/resume`), {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
|
||||||
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
|
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
|
||||||
import { authenticatedFetch } from "@/lib/auth-utils";
|
import { authenticatedFetch } from "@/lib/auth-utils";
|
||||||
import { getReauthEndpoint } from "@/lib/connector-telemetry";
|
import { getReauthEndpoint } from "@/lib/connector-telemetry";
|
||||||
import { BACKEND_URL } from "@/lib/env-config";
|
import { buildBackendUrl } from "@/lib/env-config";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { DateRangeSelector } from "../../components/date-range-selector";
|
import { DateRangeSelector } from "../../components/date-range-selector";
|
||||||
import { PeriodicSyncConfig } from "../../components/periodic-sync-config";
|
import { PeriodicSyncConfig } from "../../components/periodic-sync-config";
|
||||||
|
|
@ -95,12 +95,13 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
|
||||||
if (!spaceId || !reauthEndpoint) return;
|
if (!spaceId || !reauthEndpoint) return;
|
||||||
setReauthing(true);
|
setReauthing(true);
|
||||||
try {
|
try {
|
||||||
const backendUrl = BACKEND_URL;
|
const response = await authenticatedFetch(
|
||||||
const url = new URL(`${backendUrl}${reauthEndpoint}`);
|
buildBackendUrl(reauthEndpoint, {
|
||||||
url.searchParams.set("connector_id", String(connector.id));
|
connector_id: connector.id,
|
||||||
url.searchParams.set("space_id", String(spaceId));
|
space_id: spaceId,
|
||||||
url.searchParams.set("return_url", window.location.pathname);
|
return_url: window.location.pathname,
|
||||||
const response = await authenticatedFetch(url.toString());
|
})
|
||||||
|
);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const data = await response.json().catch(() => ({}));
|
const data = await response.json().catch(() => ({}));
|
||||||
toast.error(data.detail ?? "Failed to initiate re-authentication.");
|
toast.error(data.detail ?? "Failed to initiate re-authentication.");
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
|
||||||
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
|
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
|
||||||
import { authenticatedFetch } from "@/lib/auth-utils";
|
import { authenticatedFetch } from "@/lib/auth-utils";
|
||||||
import { getReauthEndpoint } from "@/lib/connector-telemetry";
|
import { getReauthEndpoint } from "@/lib/connector-telemetry";
|
||||||
import { BACKEND_URL } from "@/lib/env-config";
|
import { buildBackendUrl } from "@/lib/env-config";
|
||||||
import { formatRelativeDate } from "@/lib/format-date";
|
import { formatRelativeDate } from "@/lib/format-date";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { LIVE_CONNECTOR_TYPES } from "../constants/connector-constants";
|
import { LIVE_CONNECTOR_TYPES } from "../constants/connector-constants";
|
||||||
|
|
@ -61,12 +61,13 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
|
||||||
if (!searchSpaceId || !endpoint) return;
|
if (!searchSpaceId || !endpoint) return;
|
||||||
setReauthingId(connector.id);
|
setReauthingId(connector.id);
|
||||||
try {
|
try {
|
||||||
const backendUrl = BACKEND_URL;
|
const response = await authenticatedFetch(
|
||||||
const url = new URL(`${backendUrl}${endpoint}`);
|
buildBackendUrl(endpoint, {
|
||||||
url.searchParams.set("connector_id", String(connector.id));
|
connector_id: connector.id,
|
||||||
url.searchParams.set("space_id", String(searchSpaceId));
|
space_id: searchSpaceId,
|
||||||
url.searchParams.set("return_url", window.location.pathname);
|
return_url: window.location.pathname,
|
||||||
const response = await authenticatedFetch(url.toString());
|
})
|
||||||
|
);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const data = await response.json().catch(() => ({}));
|
const data = await response.json().catch(() => ({}));
|
||||||
toast.error(data.detail ?? "Failed to initiate re-authentication.");
|
toast.error(data.detail ?? "Failed to initiate re-authentication.");
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ import { useMediaQuery } from "@/hooks/use-media-query";
|
||||||
import { useElectronAPI } from "@/hooks/use-platform";
|
import { useElectronAPI } from "@/hooks/use-platform";
|
||||||
import { authenticatedFetch, getBearerToken, redirectToLogin } from "@/lib/auth-utils";
|
import { authenticatedFetch, getBearerToken, redirectToLogin } from "@/lib/auth-utils";
|
||||||
import { inferMonacoLanguageFromPath } from "@/lib/editor-language";
|
import { inferMonacoLanguageFromPath } from "@/lib/editor-language";
|
||||||
import { BACKEND_URL } from "@/lib/env-config";
|
import { buildBackendUrl } from "@/lib/env-config";
|
||||||
|
|
||||||
const PlateEditor = dynamic(
|
const PlateEditor = dynamic(
|
||||||
() => import("@/components/editor/plate-editor").then((m) => ({ default: m.PlateEditor })),
|
() => import("@/components/editor/plate-editor").then((m) => ({ default: m.PlateEditor })),
|
||||||
|
|
@ -260,10 +260,12 @@ export function EditorPanelContent({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = new URL(
|
const response = await authenticatedFetch(
|
||||||
`${BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/documents/${documentId}/editor-content`
|
buildBackendUrl(
|
||||||
|
`/api/v1/search-spaces/${searchSpaceId}/documents/${documentId}/editor-content`
|
||||||
|
),
|
||||||
|
{ method: "GET" }
|
||||||
);
|
);
|
||||||
const response = await authenticatedFetch(url.toString(), { method: "GET" });
|
|
||||||
|
|
||||||
if (controller.signal.aborted) return;
|
if (controller.signal.aborted) return;
|
||||||
|
|
||||||
|
|
@ -402,7 +404,7 @@ export function EditorPanelContent({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const response = await authenticatedFetch(
|
const response = await authenticatedFetch(
|
||||||
`${BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/documents/${documentId}/save`,
|
buildBackendUrl(`/api/v1/search-spaces/${searchSpaceId}/documents/${documentId}/save`),
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
|
|
@ -496,7 +498,9 @@ export function EditorPanelContent({
|
||||||
setDownloading(true);
|
setDownloading(true);
|
||||||
try {
|
try {
|
||||||
const response = await authenticatedFetch(
|
const response = await authenticatedFetch(
|
||||||
`${BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/documents/${documentId}/download-markdown`,
|
buildBackendUrl(
|
||||||
|
`/api/v1/search-spaces/${searchSpaceId}/documents/${documentId}/download-markdown`
|
||||||
|
),
|
||||||
{ method: "GET" }
|
{ method: "GET" }
|
||||||
);
|
);
|
||||||
if (!response.ok) throw new Error("Download failed");
|
if (!response.ok) throw new Error("Download failed");
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Spinner } from "@/components/ui/spinner";
|
import { Spinner } from "@/components/ui/spinner";
|
||||||
import { authenticatedFetch, getBearerToken, redirectToLogin } from "@/lib/auth-utils";
|
import { authenticatedFetch, getBearerToken, redirectToLogin } from "@/lib/auth-utils";
|
||||||
import { BACKEND_URL } from "@/lib/env-config";
|
import { buildBackendUrl } from "@/lib/env-config";
|
||||||
|
|
||||||
const LARGE_DOCUMENT_THRESHOLD = 2 * 1024 * 1024; // 2MB
|
const LARGE_DOCUMENT_THRESHOLD = 2 * 1024 * 1024; // 2MB
|
||||||
|
|
||||||
|
|
@ -108,10 +108,12 @@ export function DocumentTabContent({ documentId, searchSpaceId, title }: Documen
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const url = new URL(
|
const response = await authenticatedFetch(
|
||||||
`${BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/documents/${documentId}/editor-content`
|
buildBackendUrl(
|
||||||
|
`/api/v1/search-spaces/${searchSpaceId}/documents/${documentId}/editor-content`
|
||||||
|
),
|
||||||
|
{ method: "GET" }
|
||||||
);
|
);
|
||||||
const response = await authenticatedFetch(url.toString(), { method: "GET" });
|
|
||||||
|
|
||||||
if (controller.signal.aborted) return;
|
if (controller.signal.aborted) return;
|
||||||
|
|
||||||
|
|
@ -165,7 +167,7 @@ export function DocumentTabContent({ documentId, searchSpaceId, title }: Documen
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
try {
|
try {
|
||||||
const response = await authenticatedFetch(
|
const response = await authenticatedFetch(
|
||||||
`${BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/documents/${documentId}/save`,
|
buildBackendUrl(`/api/v1/search-spaces/${searchSpaceId}/documents/${documentId}/save`),
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
|
|
@ -323,7 +325,9 @@ export function DocumentTabContent({ documentId, searchSpaceId, title }: Documen
|
||||||
setDownloading(true);
|
setDownloading(true);
|
||||||
try {
|
try {
|
||||||
const response = await authenticatedFetch(
|
const response = await authenticatedFetch(
|
||||||
`${BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/documents/${documentId}/download-markdown`,
|
buildBackendUrl(
|
||||||
|
`/api/v1/search-spaces/${searchSpaceId}/documents/${documentId}/download-markdown`
|
||||||
|
),
|
||||||
{ method: "GET" }
|
{ method: "GET" }
|
||||||
);
|
);
|
||||||
if (!response.ok) throw new Error("Download failed");
|
if (!response.ok) throw new Error("Download failed");
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,32 @@ const compat = new FlatCompat({
|
||||||
baseDirectory: __dirname,
|
baseDirectory: __dirname,
|
||||||
});
|
});
|
||||||
|
|
||||||
const eslintConfig = [...compat.extends("next/core-web-vitals", "next/typescript")];
|
const eslintConfig = [
|
||||||
|
...compat.extends("next/core-web-vitals", "next/typescript"),
|
||||||
|
{
|
||||||
|
rules: {
|
||||||
|
"no-restricted-syntax": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
selector:
|
||||||
|
"NewExpression[callee.name='URL'] TemplateLiteral Identifier[name='BACKEND_URL']",
|
||||||
|
message:
|
||||||
|
"Use buildBackendUrl(path, params) for backend URLs. BACKEND_URL may be empty in proxy mode, and new URL('/relative') throws without a base.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector:
|
||||||
|
"NewExpression[callee.name='URL'] TemplateLiteral Identifier[name='backendUrl']",
|
||||||
|
message:
|
||||||
|
"Use buildBackendUrl(path, params) for backend URLs instead of aliasing BACKEND_URL into new URL().",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: "VariableDeclarator[id.name='backendUrl'][init.name='BACKEND_URL']",
|
||||||
|
message:
|
||||||
|
"Do not alias BACKEND_URL for URL construction. Use buildBackendUrl(path, params) instead.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export default eslintConfig;
|
export default eslintConfig;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { authenticatedFetch } from "@/lib/auth-utils";
|
import { authenticatedFetch } from "@/lib/auth-utils";
|
||||||
import { BACKEND_URL } from "@/lib/env-config";
|
import { buildBackendUrl } from "@/lib/env-config";
|
||||||
export interface SearchSourceConnector {
|
export interface SearchSourceConnector {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
|
@ -106,16 +106,15 @@ export const useSearchSourceConnectors = (lazy: boolean = false, searchSpaceId?:
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
// Build URL with optional search_space_id query parameter
|
const response = await authenticatedFetch(
|
||||||
const url = new URL(`${BACKEND_URL}/api/v1/search-source-connectors`);
|
buildBackendUrl("/api/v1/search-source-connectors", {
|
||||||
if (spaceId !== undefined) {
|
search_space_id: spaceId,
|
||||||
url.searchParams.append("search_space_id", spaceId.toString());
|
}),
|
||||||
}
|
{
|
||||||
|
method: "GET",
|
||||||
const response = await authenticatedFetch(url.toString(), {
|
headers: { "Content-Type": "application/json" },
|
||||||
method: "GET",
|
}
|
||||||
headers: { "Content-Type": "application/json" },
|
);
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Failed to fetch connectors: ${response.statusText}`);
|
throw new Error(`Failed to fetch connectors: ${response.statusText}`);
|
||||||
|
|
@ -166,15 +165,16 @@ export const useSearchSourceConnectors = (lazy: boolean = false, searchSpaceId?:
|
||||||
spaceId: number
|
spaceId: number
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
// Add search_space_id as a query parameter
|
const response = await authenticatedFetch(
|
||||||
const url = new URL(`${BACKEND_URL}/api/v1/search-source-connectors`);
|
buildBackendUrl("/api/v1/search-source-connectors", {
|
||||||
url.searchParams.append("search_space_id", spaceId.toString());
|
search_space_id: spaceId,
|
||||||
|
}),
|
||||||
const response = await authenticatedFetch(url.toString(), {
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify(connectorData),
|
body: JSON.stringify(connectorData),
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Failed to create connector: ${response.statusText}`);
|
throw new Error(`Failed to create connector: ${response.statusText}`);
|
||||||
|
|
@ -204,7 +204,7 @@ export const useSearchSourceConnectors = (lazy: boolean = false, searchSpaceId?:
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const response = await authenticatedFetch(
|
const response = await authenticatedFetch(
|
||||||
`${BACKEND_URL}/api/v1/search-source-connectors/${connectorId}`,
|
buildBackendUrl(`/api/v1/search-source-connectors/${connectorId}`),
|
||||||
{
|
{
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
|
|
@ -235,7 +235,7 @@ export const useSearchSourceConnectors = (lazy: boolean = false, searchSpaceId?:
|
||||||
const deleteConnector = async (connectorId: number) => {
|
const deleteConnector = async (connectorId: number) => {
|
||||||
try {
|
try {
|
||||||
const response = await authenticatedFetch(
|
const response = await authenticatedFetch(
|
||||||
`${BACKEND_URL}/api/v1/search-source-connectors/${connectorId}`,
|
buildBackendUrl(`/api/v1/search-source-connectors/${connectorId}`),
|
||||||
{
|
{
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
|
|
@ -267,19 +267,12 @@ export const useSearchSourceConnectors = (lazy: boolean = false, searchSpaceId?:
|
||||||
endDate?: string
|
endDate?: string
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
// Build query parameters
|
|
||||||
const params = new URLSearchParams({
|
|
||||||
search_space_id: searchSpaceId.toString(),
|
|
||||||
});
|
|
||||||
if (startDate) {
|
|
||||||
params.append("start_date", startDate);
|
|
||||||
}
|
|
||||||
if (endDate) {
|
|
||||||
params.append("end_date", endDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await authenticatedFetch(
|
const response = await authenticatedFetch(
|
||||||
`${BACKEND_URL}/api/v1/search-source-connectors/${connectorId}/index?${params.toString()}`,
|
buildBackendUrl(`/api/v1/search-source-connectors/${connectorId}/index`, {
|
||||||
|
search_space_id: searchSpaceId,
|
||||||
|
start_date: startDate,
|
||||||
|
end_date: endDate,
|
||||||
|
}),
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import type { ZodType } from "zod";
|
import type { ZodType } from "zod";
|
||||||
import { BACKEND_URL } from "@/lib/env-config";
|
import { buildBackendUrl } from "@/lib/env-config";
|
||||||
import { getClientPlatform } from "../agent-filesystem";
|
import { getClientPlatform } from "../agent-filesystem";
|
||||||
import { getBearerToken, handleUnauthorized, refreshAccessToken } from "../auth-utils";
|
import { getBearerToken, handleUnauthorized, refreshAccessToken } from "../auth-utils";
|
||||||
import {
|
import {
|
||||||
|
|
@ -31,8 +31,6 @@ export type RequestOptions = {
|
||||||
};
|
};
|
||||||
|
|
||||||
class BaseApiService {
|
class BaseApiService {
|
||||||
baseUrl: string;
|
|
||||||
|
|
||||||
noAuthEndpoints: string[] = ["/auth/jwt/login", "/auth/register", "/auth/refresh"];
|
noAuthEndpoints: string[] = ["/auth/jwt/login", "/auth/register", "/auth/refresh"];
|
||||||
|
|
||||||
// Prefixes that don't require auth (checked with startsWith)
|
// Prefixes that don't require auth (checked with startsWith)
|
||||||
|
|
@ -44,12 +42,9 @@ class BaseApiService {
|
||||||
return typeof window !== "undefined" ? getBearerToken() || "" : "";
|
return typeof window !== "undefined" ? getBearerToken() || "" : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(baseUrl: string) {
|
|
||||||
this.baseUrl = baseUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep for backward compatibility, but token is now always read from localStorage
|
// Keep for backward compatibility, but token is now always read from localStorage
|
||||||
setBearerToken(_bearerToken: string) {
|
setBearerToken(_bearerToken: string) {
|
||||||
|
void _bearerToken;
|
||||||
// No-op: token is now always read fresh from localStorage via the getter
|
// No-op: token is now always read fresh from localStorage via the getter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,8 +97,7 @@ class BaseApiService {
|
||||||
throw new AuthenticationError("You are not authenticated. Please login again.");
|
throw new AuthenticationError("You are not authenticated. Please login again.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct the full URL. Empty baseUrl is valid for same-origin proxy mode.
|
const fullUrl = buildBackendUrl(url);
|
||||||
const fullUrl = this.baseUrl ? new URL(url, this.baseUrl).toString() : url;
|
|
||||||
|
|
||||||
// Prepare fetch options
|
// Prepare fetch options
|
||||||
const fetchOptions: RequestInit = {
|
const fetchOptions: RequestInit = {
|
||||||
|
|
@ -379,7 +373,8 @@ class BaseApiService {
|
||||||
options?: Omit<RequestOptions, "method" | "responseType" | "body"> & { body: FormData }
|
options?: Omit<RequestOptions, "method" | "responseType" | "body"> & { body: FormData }
|
||||||
) {
|
) {
|
||||||
// Remove Content-Type from options headers if present
|
// Remove Content-Type from options headers if present
|
||||||
const { "Content-Type": _, ...headersWithoutContentType } = options?.headers ?? {};
|
const headersWithoutContentType = { ...(options?.headers ?? {}) };
|
||||||
|
delete headersWithoutContentType["Content-Type"];
|
||||||
|
|
||||||
return this.request(url, responseSchema, {
|
return this.request(url, responseSchema, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -394,4 +389,4 @@ class BaseApiService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const baseApiService = new BaseApiService(BACKEND_URL);
|
export const baseApiService = new BaseApiService();
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,44 @@ export const AUTH_TYPE = process.env.NEXT_PUBLIC_FASTAPI_BACKEND_AUTH_TYPE || "G
|
||||||
// same-origin relative requests (e.g. /api/v1/... and /auth/...).
|
// same-origin relative requests (e.g. /api/v1/... and /auth/...).
|
||||||
export const BACKEND_URL = process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL ?? "";
|
export const BACKEND_URL = process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL ?? "";
|
||||||
|
|
||||||
|
type BackendUrlParam = string | number | boolean | null | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build browser-facing backend URLs without breaking proxy mode.
|
||||||
|
*
|
||||||
|
* In proxy mode BACKEND_URL intentionally stays empty, so callers must keep
|
||||||
|
* same-origin relative URLs ("/api/v1/...") and let Caddy route them. When
|
||||||
|
* BACKEND_URL is explicitly configured, the same path resolves against that
|
||||||
|
* absolute backend origin.
|
||||||
|
*/
|
||||||
|
export function buildBackendUrl(
|
||||||
|
path: string,
|
||||||
|
params?: Record<string, BackendUrlParam>
|
||||||
|
): string {
|
||||||
|
const backendPath = path.startsWith("/") ? path : `/${path}`;
|
||||||
|
const queryParams = new URLSearchParams();
|
||||||
|
|
||||||
|
if (params) {
|
||||||
|
for (const [key, value] of Object.entries(params)) {
|
||||||
|
if (value !== null && value !== undefined) {
|
||||||
|
queryParams.append(key, String(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BACKEND_URL) {
|
||||||
|
const url = new URL(backendPath, BACKEND_URL);
|
||||||
|
for (const [key, value] of queryParams) {
|
||||||
|
url.searchParams.append(key, value);
|
||||||
|
}
|
||||||
|
return url.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryString = queryParams.toString();
|
||||||
|
if (!queryString) return backendPath;
|
||||||
|
return `${backendPath}${backendPath.includes("?") ? "&" : "?"}${queryString}`;
|
||||||
|
}
|
||||||
|
|
||||||
// Server-side backend URL. Relative browser URLs do not work from RSC/API route
|
// Server-side backend URL. Relative browser URLs do not work from RSC/API route
|
||||||
// code, so server callers should use Docker DNS or an explicit public backend.
|
// code, so server callers should use Docker DNS or an explicit public backend.
|
||||||
export const SERVER_BACKEND_URL =
|
export const SERVER_BACKEND_URL =
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue