mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-06-18 21:15:16 +02:00
refactor(web): support same-origin backend and zero urls
This commit is contained in:
parent
2373014943
commit
f5d04cf8ba
11 changed files with 62 additions and 36 deletions
|
|
@ -7,7 +7,7 @@ import { FAQJsonLd, JsonLd } from "@/components/seo/json-ld";
|
|||
import { Button } from "@/components/ui/button";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import type { AnonModel } from "@/contracts/types/anonymous-chat.types";
|
||||
import { BACKEND_URL } from "@/lib/env-config";
|
||||
import { SERVER_BACKEND_URL } from "@/lib/env-config";
|
||||
|
||||
interface PageProps {
|
||||
params: Promise<{ model_slug: string }>;
|
||||
|
|
@ -16,7 +16,7 @@ interface PageProps {
|
|||
async function getModel(slug: string): Promise<AnonModel | null> {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${BACKEND_URL}/api/v1/public/anon-chat/models/${encodeURIComponent(slug)}`,
|
||||
`${SERVER_BACKEND_URL}/api/v1/public/anon-chat/models/${encodeURIComponent(slug)}`,
|
||||
{ next: { revalidate: 300 } }
|
||||
);
|
||||
if (!res.ok) return null;
|
||||
|
|
@ -28,7 +28,7 @@ async function getModel(slug: string): Promise<AnonModel | null> {
|
|||
|
||||
async function getAllModels(): Promise<AnonModel[]> {
|
||||
try {
|
||||
const res = await fetch(`${BACKEND_URL}/api/v1/public/anon-chat/models`, {
|
||||
const res = await fetch(`${SERVER_BACKEND_URL}/api/v1/public/anon-chat/models`, {
|
||||
next: { revalidate: 300 },
|
||||
});
|
||||
if (!res.ok) return [];
|
||||
|
|
@ -136,7 +136,7 @@ export async function generateMetadata({ params }: PageProps): Promise<Metadata>
|
|||
|
||||
export async function generateStaticParams() {
|
||||
const models = await getAllModels();
|
||||
return models.filter((m) => m.seo_slug).map((m) => ({ model_slug: m.seo_slug! }));
|
||||
return models.flatMap((m) => (m.seo_slug ? [{ model_slug: m.seo_slug }] : []));
|
||||
}
|
||||
|
||||
export default async function FreeModelPage({ params }: PageProps) {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import {
|
|||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import type { AnonModel } from "@/contracts/types/anonymous-chat.types";
|
||||
import { BACKEND_URL } from "@/lib/env-config";
|
||||
import { SERVER_BACKEND_URL } from "@/lib/env-config";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Free AI Chat, No Login Required | SurfSense",
|
||||
|
|
@ -94,7 +94,7 @@ export const metadata: Metadata = {
|
|||
|
||||
async function getModels(): Promise<AnonModel[]> {
|
||||
try {
|
||||
const res = await fetch(`${BACKEND_URL}/api/v1/public/anon-chat/models`, {
|
||||
const res = await fetch(`${SERVER_BACKEND_URL}/api/v1/public/anon-chat/models`, {
|
||||
next: { revalidate: 300 },
|
||||
});
|
||||
if (!res.ok) return [];
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { mustGetQuery } from "@rocicorp/zero";
|
||||
import { handleQueryRequest } from "@rocicorp/zero/server";
|
||||
import { NextResponse } from "next/server";
|
||||
import { BACKEND_URL } from "@/lib/env-config";
|
||||
import { SERVER_BACKEND_URL } from "@/lib/env-config";
|
||||
import type { Context } from "@/types/zero";
|
||||
import { queries } from "@/zero/queries";
|
||||
import { schema } from "@/zero/schema";
|
||||
|
|
@ -11,11 +11,7 @@ import { schema } from "@/zero/schema";
|
|||
// (e.g. http://backend:8000). The browser-facing NEXT_PUBLIC_FASTAPI_BACKEND_URL
|
||||
// (e.g. http://localhost:8929) does NOT resolve from inside the frontend
|
||||
// container and would make every authenticated Zero query fail with a 503.
|
||||
const backendURL = (
|
||||
process.env.FASTAPI_BACKEND_INTERNAL_URL ||
|
||||
process.env.BACKEND_URL ||
|
||||
"http://localhost:8000"
|
||||
).replace(/\/$/, "");
|
||||
const backendURL = SERVER_BACKEND_URL.replace(/\/$/, "");
|
||||
|
||||
async function authenticateRequest(
|
||||
request: Request
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { loader } from "fumadocs-core/source";
|
|||
import type { MetadataRoute } from "next";
|
||||
import { blog, changelog } from "@/.source/server";
|
||||
import { source as docsSource } from "@/lib/source";
|
||||
import { SERVER_BACKEND_URL } from "@/lib/env-config";
|
||||
|
||||
const blogSource = loader({
|
||||
baseUrl: "/blog",
|
||||
|
|
@ -14,11 +15,10 @@ const changelogSource = loader({
|
|||
});
|
||||
|
||||
const BASE_URL = "https://www.surfsense.com";
|
||||
const BACKEND_URL = process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL || "http://localhost:8000";
|
||||
|
||||
async function getFreeModelSlugs(): Promise<string[]> {
|
||||
try {
|
||||
const res = await fetch(`${BACKEND_URL}/api/v1/public/anon-chat/models`, {
|
||||
const res = await fetch(`${SERVER_BACKEND_URL}/api/v1/public/anon-chat/models`, {
|
||||
next: { revalidate: 3600 },
|
||||
});
|
||||
if (!res.ok) return [];
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { authenticatedFetch } from "@/lib/auth-utils";
|
||||
import { BACKEND_URL } from "@/lib/env-config";
|
||||
|
||||
export type MemoryScope = "user" | "team";
|
||||
|
||||
|
|
@ -30,7 +31,7 @@ function getMemoryPath(scope: MemoryScope, searchSpaceId?: number | null) {
|
|||
}
|
||||
|
||||
function getBackendUrl(path: string) {
|
||||
return `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}${path}`;
|
||||
return `${BACKEND_URL}${path}`;
|
||||
}
|
||||
|
||||
export function getMemoryLimitState(length: number, limits?: MemoryLimits | null) {
|
||||
|
|
|
|||
|
|
@ -820,8 +820,8 @@ function AuthenticatedDocumentsSidebarBase({
|
|||
try {
|
||||
const endpoint =
|
||||
doc.document_type === "USER_MEMORY"
|
||||
? `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/users/me/memory`
|
||||
: `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}/memory`;
|
||||
? `${BACKEND_URL}/api/v1/users/me/memory`
|
||||
: `${BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}/memory`;
|
||||
const response = await authenticatedFetch(endpoint, { method: "GET" });
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({ detail: "Export failed" }));
|
||||
|
|
@ -1028,8 +1028,8 @@ function AuthenticatedDocumentsSidebarBase({
|
|||
}
|
||||
const endpoint =
|
||||
doc.document_type === "USER_MEMORY"
|
||||
? `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/users/me/memory/reset`
|
||||
: `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}/memory/reset`;
|
||||
? `${BACKEND_URL}/api/v1/users/me/memory/reset`
|
||||
: `${BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}/memory/reset`;
|
||||
try {
|
||||
const response = await authenticatedFetch(endpoint, { method: "POST" });
|
||||
if (!response.ok) {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,15 @@ import { getBearerToken, handleUnauthorized, refreshAccessToken } from "@/lib/au
|
|||
import { queries } from "@/zero/queries";
|
||||
import { schema } from "@/zero/schema";
|
||||
|
||||
const cacheURL = process.env.NEXT_PUBLIC_ZERO_CACHE_URL || "http://localhost:4848";
|
||||
const configuredCacheURL = process.env.NEXT_PUBLIC_ZERO_CACHE_URL;
|
||||
|
||||
function getCacheURL() {
|
||||
if (configuredCacheURL) return configuredCacheURL;
|
||||
if (typeof window !== "undefined") {
|
||||
return `${window.location.origin}/zero`;
|
||||
}
|
||||
return "http://localhost:4848";
|
||||
}
|
||||
|
||||
function ZeroAuthSync() {
|
||||
const zero = useZero();
|
||||
|
|
@ -42,6 +50,7 @@ function ZeroAuthSync() {
|
|||
|
||||
export function ZeroProvider({ children }: { children: React.ReactNode }) {
|
||||
const { data: user } = useAtomValue(currentUserAtom);
|
||||
const cacheURL = useMemo(() => getCacheURL(), []);
|
||||
|
||||
const userId = user?.id;
|
||||
const hasUser = !!userId;
|
||||
|
|
@ -65,7 +74,7 @@ export function ZeroProvider({ children }: { children: React.ReactNode }) {
|
|||
cacheURL,
|
||||
auth,
|
||||
}),
|
||||
[userID, context, auth]
|
||||
[userID, context, cacheURL, auth]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -12,21 +12,31 @@
|
|||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
function envValue(name, fallback, { allowEmpty = false } = {}) {
|
||||
if (Object.hasOwn(process.env, name)) {
|
||||
const value = process.env[name];
|
||||
if (allowEmpty || value) {
|
||||
return value ?? "";
|
||||
}
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
const replacements = [
|
||||
[
|
||||
"__NEXT_PUBLIC_FASTAPI_BACKEND_URL__",
|
||||
process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL || "http://localhost:8000",
|
||||
envValue("NEXT_PUBLIC_FASTAPI_BACKEND_URL", "http://localhost:8000", { allowEmpty: true }),
|
||||
],
|
||||
[
|
||||
"__NEXT_PUBLIC_FASTAPI_BACKEND_AUTH_TYPE__",
|
||||
process.env.NEXT_PUBLIC_FASTAPI_BACKEND_AUTH_TYPE || "LOCAL",
|
||||
envValue("NEXT_PUBLIC_FASTAPI_BACKEND_AUTH_TYPE", "LOCAL"),
|
||||
],
|
||||
["__NEXT_PUBLIC_ETL_SERVICE__", process.env.NEXT_PUBLIC_ETL_SERVICE || "DOCLING"],
|
||||
["__NEXT_PUBLIC_ETL_SERVICE__", envValue("NEXT_PUBLIC_ETL_SERVICE", "DOCLING")],
|
||||
[
|
||||
"__NEXT_PUBLIC_ZERO_CACHE_URL__",
|
||||
process.env.NEXT_PUBLIC_ZERO_CACHE_URL || "http://localhost:4848",
|
||||
envValue("NEXT_PUBLIC_ZERO_CACHE_URL", "http://localhost:4848", { allowEmpty: true }),
|
||||
],
|
||||
["__NEXT_PUBLIC_DEPLOYMENT_MODE__", process.env.NEXT_PUBLIC_DEPLOYMENT_MODE || "self-hosted"],
|
||||
["__NEXT_PUBLIC_DEPLOYMENT_MODE__", envValue("NEXT_PUBLIC_DEPLOYMENT_MODE", "self-hosted")],
|
||||
];
|
||||
|
||||
let filesProcessed = 0;
|
||||
|
|
|
|||
|
|
@ -93,11 +93,6 @@ class BaseApiService {
|
|||
},
|
||||
};
|
||||
|
||||
// Validate the base URL
|
||||
if (!this.baseUrl) {
|
||||
throw new AppError("Base URL is not set.");
|
||||
}
|
||||
|
||||
// Validate the bearer token
|
||||
const isNoAuthEndpoint =
|
||||
this.noAuthEndpoints.includes(url) ||
|
||||
|
|
@ -107,8 +102,8 @@ class BaseApiService {
|
|||
throw new AuthenticationError("You are not authenticated. Please login again.");
|
||||
}
|
||||
|
||||
// Construct the full URL
|
||||
const fullUrl = new URL(url, this.baseUrl).toString();
|
||||
// Construct the full URL. Empty baseUrl is valid for same-origin proxy mode.
|
||||
const fullUrl = this.baseUrl ? new URL(url, this.baseUrl).toString() : url;
|
||||
|
||||
// Prepare fetch options
|
||||
const fetchOptions: RequestInit = {
|
||||
|
|
|
|||
|
|
@ -15,9 +15,18 @@ import packageJson from "../package.json";
|
|||
// Placeholder: __NEXT_PUBLIC_FASTAPI_BACKEND_AUTH_TYPE__
|
||||
export const AUTH_TYPE = process.env.NEXT_PUBLIC_FASTAPI_BACKEND_AUTH_TYPE || "GOOGLE";
|
||||
|
||||
// Backend API URL
|
||||
// Backend API URL. An empty string is valid in proxy mode and means
|
||||
// same-origin relative requests (e.g. /api/v1/... and /auth/...).
|
||||
// Placeholder: __NEXT_PUBLIC_FASTAPI_BACKEND_URL__
|
||||
export const BACKEND_URL = process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL || "http://localhost:8000";
|
||||
export const BACKEND_URL = process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL ?? "http://localhost:8000";
|
||||
|
||||
// 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.
|
||||
export const SERVER_BACKEND_URL =
|
||||
process.env.FASTAPI_BACKEND_INTERNAL_URL ||
|
||||
process.env.BACKEND_URL ||
|
||||
BACKEND_URL ||
|
||||
"http://localhost:8000";
|
||||
|
||||
// ETL Service: "DOCLING", "UNSTRUCTURED", or "LLAMACLOUD"
|
||||
// Placeholder: __NEXT_PUBLIC_ETL_SERVICE__
|
||||
|
|
|
|||
|
|
@ -2,12 +2,17 @@ import { defineConfig, devices } from "@playwright/test";
|
|||
|
||||
const PORT = process.env.PORT || "3000";
|
||||
const BACKEND_PORT = process.env.BACKEND_PORT || "8000";
|
||||
const ZERO_CACHE_PORT = process.env.ZERO_CACHE_PORT || "4848";
|
||||
const baseURL = process.env.PLAYWRIGHT_BASE_URL || `http://localhost:${PORT}`;
|
||||
const useProxyOrigin = process.env.PLAYWRIGHT_USE_PROXY_ORIGIN === "true";
|
||||
const backendURL = useProxyOrigin ? baseURL : `http://localhost:${BACKEND_PORT}`;
|
||||
const zeroCacheURL = useProxyOrigin ? `${baseURL}/zero` : `http://localhost:${ZERO_CACHE_PORT}`;
|
||||
|
||||
process.env.PLAYWRIGHT_TEST_EMAIL ??= "e2e-test@surfsense.net";
|
||||
process.env.PLAYWRIGHT_TEST_PASSWORD ??= "E2eTestPassword123!";
|
||||
process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL ??= `http://localhost:${BACKEND_PORT}`;
|
||||
process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL ??= backendURL;
|
||||
process.env.NEXT_PUBLIC_FASTAPI_BACKEND_AUTH_TYPE ??= "LOCAL";
|
||||
process.env.NEXT_PUBLIC_ZERO_CACHE_URL ??= zeroCacheURL;
|
||||
|
||||
/**
|
||||
* Playwright configuration for SurfSense web E2E tests.
|
||||
|
|
@ -68,6 +73,7 @@ export default defineConfig({
|
|||
env: {
|
||||
NEXT_PUBLIC_FASTAPI_BACKEND_URL: process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL,
|
||||
NEXT_PUBLIC_FASTAPI_BACKEND_AUTH_TYPE: process.env.NEXT_PUBLIC_FASTAPI_BACKEND_AUTH_TYPE,
|
||||
NEXT_PUBLIC_ZERO_CACHE_URL: process.env.NEXT_PUBLIC_ZERO_CACHE_URL,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue