mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-21 18:55:16 +02:00
Merge pull request #516 from MODSetter/dev
refactor: centralize authentication handling
This commit is contained in:
commit
b32ed6c1f8
35 changed files with 396 additions and 497 deletions
|
|
@ -22,6 +22,7 @@ import {
|
||||||
type SearchSourceConnector,
|
type SearchSourceConnector,
|
||||||
useSearchSourceConnectors,
|
useSearchSourceConnectors,
|
||||||
} from "@/hooks/use-search-source-connectors";
|
} from "@/hooks/use-search-source-connectors";
|
||||||
|
import { authenticatedFetch } from "@/lib/auth-utils";
|
||||||
|
|
||||||
export default function AirtableConnectorPage() {
|
export default function AirtableConnectorPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
@ -46,14 +47,9 @@ export default function AirtableConnectorPage() {
|
||||||
const handleConnectAirtable = async () => {
|
const handleConnectAirtable = async () => {
|
||||||
setIsConnecting(true);
|
setIsConnecting(true);
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/auth/airtable/connector/add/?space_id=${searchSpaceId}`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/auth/airtable/connector/add/?space_id=${searchSpaceId}`,
|
||||||
{
|
{ method: "GET" }
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ import { EnumConnectorName } from "@/contracts/enums/connector";
|
||||||
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
|
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
|
||||||
// Assuming useSearchSourceConnectors hook exists and works similarly
|
// Assuming useSearchSourceConnectors hook exists and works similarly
|
||||||
import { useSearchSourceConnectors } from "@/hooks/use-search-source-connectors";
|
import { useSearchSourceConnectors } from "@/hooks/use-search-source-connectors";
|
||||||
|
import { authenticatedFetch, redirectToLogin } from "@/lib/auth-utils";
|
||||||
|
|
||||||
// Define the form schema with Zod for GitHub PAT entry step
|
// Define the form schema with Zod for GitHub PAT entry step
|
||||||
const githubPatFormSchema = z.object({
|
const githubPatFormSchema = z.object({
|
||||||
|
|
@ -101,19 +102,11 @@ export default function GithubConnectorPage() {
|
||||||
setConnectorName(values.name); // Store the name
|
setConnectorName(values.name); // Store the name
|
||||||
setValidatedPat(values.github_pat); // Store the PAT temporarily
|
setValidatedPat(values.github_pat); // Store the PAT temporarily
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem("surfsense_bearer_token");
|
const response = await authenticatedFetch(
|
||||||
if (!token) {
|
|
||||||
throw new Error("No authentication token found");
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(
|
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/github/repositories`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/github/repositories`,
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: { "Content-Type": "application/json" },
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ github_pat: values.github_pat }),
|
body: JSON.stringify({ github_pat: values.github_pat }),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import {
|
||||||
type SearchSourceConnector,
|
type SearchSourceConnector,
|
||||||
useSearchSourceConnectors,
|
useSearchSourceConnectors,
|
||||||
} from "@/hooks/use-search-source-connectors";
|
} from "@/hooks/use-search-source-connectors";
|
||||||
|
import { authenticatedFetch } from "@/lib/auth-utils";
|
||||||
|
|
||||||
export default function GoogleCalendarConnectorPage() {
|
export default function GoogleCalendarConnectorPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
@ -51,14 +52,9 @@ export default function GoogleCalendarConnectorPage() {
|
||||||
try {
|
try {
|
||||||
setIsConnecting(true);
|
setIsConnecting(true);
|
||||||
// Call backend to initiate authorization flow
|
// Call backend to initiate authorization flow
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/auth/google/calendar/connector/add/?space_id=${searchSpaceId}`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/auth/google/calendar/connector/add/?space_id=${searchSpaceId}`,
|
||||||
{
|
{ method: "GET" }
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import {
|
||||||
type SearchSourceConnector,
|
type SearchSourceConnector,
|
||||||
useSearchSourceConnectors,
|
useSearchSourceConnectors,
|
||||||
} from "@/hooks/use-search-source-connectors";
|
} from "@/hooks/use-search-source-connectors";
|
||||||
|
import { authenticatedFetch } from "@/lib/auth-utils";
|
||||||
|
|
||||||
export default function GoogleGmailConnectorPage() {
|
export default function GoogleGmailConnectorPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
@ -50,14 +51,9 @@ export default function GoogleGmailConnectorPage() {
|
||||||
try {
|
try {
|
||||||
setIsConnecting(true);
|
setIsConnecting(true);
|
||||||
// Call backend to initiate authorization flow
|
// Call backend to initiate authorization flow
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/auth/google/gmail/connector/add/?space_id=${searchSpaceId}`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/auth/google/gmail/connector/add/?space_id=${searchSpaceId}`,
|
||||||
{
|
{ method: "GET" }
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import { BlockNoteEditor } from "@/components/DynamicBlockNoteEditor";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
import { authenticatedFetch, getBearerToken, redirectToLogin } from "@/lib/auth-utils";
|
||||||
|
|
||||||
interface EditorContent {
|
interface EditorContent {
|
||||||
document_id: number;
|
document_id: number;
|
||||||
|
|
@ -29,28 +30,21 @@ export default function EditorPage() {
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
|
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
|
||||||
|
|
||||||
// Get auth token
|
|
||||||
const token =
|
|
||||||
typeof window !== "undefined" ? localStorage.getItem("surfsense_bearer_token") : null;
|
|
||||||
|
|
||||||
// Fetch document content - DIRECT CALL TO FASTAPI
|
// Fetch document content - DIRECT CALL TO FASTAPI
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchDocument() {
|
async function fetchDocument() {
|
||||||
|
const token = getBearerToken();
|
||||||
if (!token) {
|
if (!token) {
|
||||||
console.error("No auth token found");
|
console.error("No auth token found");
|
||||||
setError("Please login to access the editor");
|
// Redirect to login with current path saved
|
||||||
setLoading(false);
|
redirectToLogin();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-spaces/${params.search_space_id}/documents/${documentId}/editor-content`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-spaces/${params.search_space_id}/documents/${documentId}/editor-content`,
|
||||||
{
|
{ method: "GET" }
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
@ -84,10 +78,10 @@ export default function EditorPage() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (documentId && token) {
|
if (documentId) {
|
||||||
fetchDocument();
|
fetchDocument();
|
||||||
}
|
}
|
||||||
}, [documentId, token]);
|
}, [documentId, params.search_space_id]);
|
||||||
|
|
||||||
// Track changes to mark as unsaved
|
// Track changes to mark as unsaved
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -100,8 +94,10 @@ export default function EditorPage() {
|
||||||
|
|
||||||
// Save and exit - DIRECT CALL TO FASTAPI
|
// Save and exit - DIRECT CALL TO FASTAPI
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
|
const token = getBearerToken();
|
||||||
if (!token) {
|
if (!token) {
|
||||||
toast.error("Please login to save");
|
toast.error("Please login to save");
|
||||||
|
redirectToLogin();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,14 +109,11 @@ export default function EditorPage() {
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
try {
|
try {
|
||||||
// Save blocknote_document and trigger reindexing in background
|
// Save blocknote_document and trigger reindexing in background
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-spaces/${params.search_space_id}/documents/${documentId}/save`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-spaces/${params.search_space_id}/documents/${documentId}/save`,
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: { "Content-Type": "application/json" },
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ blocknote_document: editorContent }),
|
body: JSON.stringify({ blocknote_document: editorContent }),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import { OnboardLLMSetup } from "@/components/onboard/onboard-llm-setup";
|
||||||
import { OnboardLoading } from "@/components/onboard/onboard-loading";
|
import { OnboardLoading } from "@/components/onboard/onboard-loading";
|
||||||
import { OnboardStats } from "@/components/onboard/onboard-stats";
|
import { OnboardStats } from "@/components/onboard/onboard-stats";
|
||||||
import { useGlobalLLMConfigs, useLLMConfigs, useLLMPreferences } from "@/hooks/use-llm-configs";
|
import { useGlobalLLMConfigs, useLLMConfigs, useLLMPreferences } from "@/hooks/use-llm-configs";
|
||||||
|
import { getBearerToken, redirectToLogin } from "@/lib/auth-utils";
|
||||||
|
|
||||||
const OnboardPage = () => {
|
const OnboardPage = () => {
|
||||||
const t = useTranslations("onboard");
|
const t = useTranslations("onboard");
|
||||||
|
|
@ -44,12 +45,13 @@ const OnboardPage = () => {
|
||||||
|
|
||||||
// Check if user is authenticated
|
// Check if user is authenticated
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const token = localStorage.getItem("surfsense_bearer_token");
|
const token = getBearerToken();
|
||||||
if (!token) {
|
if (!token) {
|
||||||
router.push("/login");
|
// Save current path and redirect to login
|
||||||
|
redirectToLogin();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}, [router]);
|
}, []);
|
||||||
|
|
||||||
// Capture onboarding state on first load
|
// Capture onboarding state on first load
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,28 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Loader2 } from "lucide-react";
|
import { Loader2 } from "lucide-react";
|
||||||
import { useRouter } from "next/navigation";
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { AnnouncementBanner } from "@/components/announcement-banner";
|
import { AnnouncementBanner } from "@/components/announcement-banner";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
import { getBearerToken, redirectToLogin } from "@/lib/auth-utils";
|
||||||
|
|
||||||
interface DashboardLayoutProps {
|
interface DashboardLayoutProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function DashboardLayout({ children }: DashboardLayoutProps) {
|
export default function DashboardLayout({ children }: DashboardLayoutProps) {
|
||||||
const router = useRouter();
|
|
||||||
const [isCheckingAuth, setIsCheckingAuth] = useState(true);
|
const [isCheckingAuth, setIsCheckingAuth] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Check if user is authenticated
|
// Check if user is authenticated
|
||||||
const token = localStorage.getItem("surfsense_bearer_token");
|
const token = getBearerToken();
|
||||||
if (!token) {
|
if (!token) {
|
||||||
router.push("/login");
|
// Save current path and redirect to login
|
||||||
|
redirectToLogin();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setIsCheckingAuth(false);
|
setIsCheckingAuth(false);
|
||||||
}, [router]);
|
}, []);
|
||||||
|
|
||||||
// Show loading screen while checking authentication
|
// Show loading screen while checking authentication
|
||||||
if (isCheckingAuth) {
|
if (isCheckingAuth) {
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ import { Spotlight } from "@/components/ui/spotlight";
|
||||||
import { Tilt } from "@/components/ui/tilt";
|
import { Tilt } from "@/components/ui/tilt";
|
||||||
import { useUser } from "@/hooks";
|
import { useUser } from "@/hooks";
|
||||||
import { useSearchSpaces } from "@/hooks/use-search-spaces";
|
import { useSearchSpaces } from "@/hooks/use-search-spaces";
|
||||||
|
import { authenticatedFetch } from "@/lib/auth-utils";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Formats a date string into a readable format
|
* Formats a date string into a readable format
|
||||||
|
|
@ -173,14 +174,9 @@ const DashboardPage = () => {
|
||||||
const handleDeleteSearchSpace = async (id: number) => {
|
const handleDeleteSearchSpace = async (id: number) => {
|
||||||
// Send DELETE request to the API
|
// Send DELETE request to the API
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${id}`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${id}`,
|
||||||
{
|
{ method: "DELETE" }
|
||||||
method: "DELETE",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,17 @@ import { motion } from "motion/react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { SearchSpaceForm } from "@/components/search-space-form";
|
import { SearchSpaceForm } from "@/components/search-space-form";
|
||||||
|
import { authenticatedFetch } from "@/lib/auth-utils";
|
||||||
|
|
||||||
export default function SearchSpacesPage() {
|
export default function SearchSpacesPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const handleCreateSearchSpace = async (data: { name: string; description?: string }) => {
|
const handleCreateSearchSpace = async (data: { name: string; description?: string }) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces`,
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: { "Content-Type": "application/json" },
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
name: data.name,
|
name: data.name,
|
||||||
description: data.description || "",
|
description: data.description || "",
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import {
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { useInviteInfo } from "@/hooks/use-rbac";
|
import { useInviteInfo } from "@/hooks/use-rbac";
|
||||||
|
import { getBearerToken } from "@/lib/auth-utils";
|
||||||
|
|
||||||
export default function InviteAcceptPage() {
|
export default function InviteAcceptPage() {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
|
|
@ -47,7 +48,7 @@ export default function InviteAcceptPage() {
|
||||||
// Check if user is logged in
|
// Check if user is logged in
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
const token = localStorage.getItem("surfsense_bearer_token");
|
const token = getBearerToken();
|
||||||
setIsLoggedIn(!!token);
|
setIsLoggedIn(!!token);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
@ -71,7 +72,10 @@ export default function InviteAcceptPage() {
|
||||||
const handleLoginRedirect = () => {
|
const handleLoginRedirect = () => {
|
||||||
// Store the invite code to redirect back after login
|
// Store the invite code to redirect back after login
|
||||||
localStorage.setItem("pending_invite_code", inviteCode);
|
localStorage.setItem("pending_invite_code", inviteCode);
|
||||||
router.push("/auth");
|
// Save the current invite page URL so we can return after authentication
|
||||||
|
localStorage.setItem("surfsense_redirect_path", `/invite/${inviteCode}`);
|
||||||
|
// Redirect to login (we manually set the path above since invite pages need special handling)
|
||||||
|
window.location.href = "/login";
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check for pending invite after login
|
// Check for pending invite after login
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import type {
|
||||||
UpdateChatRequest,
|
UpdateChatRequest,
|
||||||
} from "@/contracts/types/chat.types";
|
} from "@/contracts/types/chat.types";
|
||||||
import { chatsApiService } from "@/lib/apis/chats-api.service";
|
import { chatsApiService } from "@/lib/apis/chats-api.service";
|
||||||
|
import { getBearerToken } from "@/lib/auth-utils";
|
||||||
import { cacheKeys } from "@/lib/query-client/cache-keys";
|
import { cacheKeys } from "@/lib/query-client/cache-keys";
|
||||||
import { queryClient } from "@/lib/query-client/client";
|
import { queryClient } from "@/lib/query-client/client";
|
||||||
import { activeSearchSpaceIdAtom } from "../seach-spaces/seach-space-queries.atom";
|
import { activeSearchSpaceIdAtom } from "../seach-spaces/seach-space-queries.atom";
|
||||||
|
|
@ -14,7 +15,7 @@ import { globalChatsQueryParamsAtom } from "./ui.atoms";
|
||||||
|
|
||||||
export const deleteChatMutationAtom = atomWithMutation((get) => {
|
export const deleteChatMutationAtom = atomWithMutation((get) => {
|
||||||
const searchSpaceId = get(activeSearchSpaceIdAtom);
|
const searchSpaceId = get(activeSearchSpaceIdAtom);
|
||||||
const authToken = localStorage.getItem("surfsense_bearer_token");
|
const authToken = getBearerToken();
|
||||||
const chatsQueryParams = get(globalChatsQueryParamsAtom);
|
const chatsQueryParams = get(globalChatsQueryParamsAtom);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -38,7 +39,7 @@ export const deleteChatMutationAtom = atomWithMutation((get) => {
|
||||||
|
|
||||||
export const createChatMutationAtom = atomWithMutation((get) => {
|
export const createChatMutationAtom = atomWithMutation((get) => {
|
||||||
const searchSpaceId = get(activeSearchSpaceIdAtom);
|
const searchSpaceId = get(activeSearchSpaceIdAtom);
|
||||||
const authToken = localStorage.getItem("surfsense_bearer_token");
|
const authToken = getBearerToken();
|
||||||
const chatsQueryParams = get(globalChatsQueryParamsAtom);
|
const chatsQueryParams = get(globalChatsQueryParamsAtom);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -58,7 +59,7 @@ export const createChatMutationAtom = atomWithMutation((get) => {
|
||||||
|
|
||||||
export const updateChatMutationAtom = atomWithMutation((get) => {
|
export const updateChatMutationAtom = atomWithMutation((get) => {
|
||||||
const searchSpaceId = get(activeSearchSpaceIdAtom);
|
const searchSpaceId = get(activeSearchSpaceIdAtom);
|
||||||
const authToken = localStorage.getItem("surfsense_bearer_token");
|
const authToken = getBearerToken();
|
||||||
const chatsQueryParams = get(globalChatsQueryParamsAtom);
|
const chatsQueryParams = get(globalChatsQueryParamsAtom);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,13 @@ import { atomWithQuery } from "jotai-tanstack-query";
|
||||||
import { activeSearchSpaceIdAtom } from "@/atoms/seach-spaces/seach-space-queries.atom";
|
import { activeSearchSpaceIdAtom } from "@/atoms/seach-spaces/seach-space-queries.atom";
|
||||||
import { chatsApiService } from "@/lib/apis/chats-api.service";
|
import { chatsApiService } from "@/lib/apis/chats-api.service";
|
||||||
import { podcastsApiService } from "@/lib/apis/podcasts-api.service";
|
import { podcastsApiService } from "@/lib/apis/podcasts-api.service";
|
||||||
|
import { getBearerToken } from "@/lib/auth-utils";
|
||||||
import { cacheKeys } from "@/lib/query-client/cache-keys";
|
import { cacheKeys } from "@/lib/query-client/cache-keys";
|
||||||
import { activeChatIdAtom, globalChatsQueryParamsAtom } from "./ui.atoms";
|
import { activeChatIdAtom, globalChatsQueryParamsAtom } from "./ui.atoms";
|
||||||
|
|
||||||
export const activeChatAtom = atomWithQuery((get) => {
|
export const activeChatAtom = atomWithQuery((get) => {
|
||||||
const activeChatId = get(activeChatIdAtom);
|
const activeChatId = get(activeChatIdAtom);
|
||||||
const authToken = localStorage.getItem("surfsense_bearer_token");
|
const authToken = getBearerToken();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
queryKey: cacheKeys.chats.activeChat(activeChatId ?? ""),
|
queryKey: cacheKeys.chats.activeChat(activeChatId ?? ""),
|
||||||
|
|
@ -32,7 +33,7 @@ export const activeChatAtom = atomWithQuery((get) => {
|
||||||
|
|
||||||
export const chatsAtom = atomWithQuery((get) => {
|
export const chatsAtom = atomWithQuery((get) => {
|
||||||
const searchSpaceId = get(activeSearchSpaceIdAtom);
|
const searchSpaceId = get(activeSearchSpaceIdAtom);
|
||||||
const authToken = localStorage.getItem("surfsense_bearer_token");
|
const authToken = getBearerToken();
|
||||||
const queryParams = get(globalChatsQueryParamsAtom);
|
const queryParams = get(globalChatsQueryParamsAtom);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,14 @@ import type {
|
||||||
Podcast,
|
Podcast,
|
||||||
} from "@/contracts/types/podcast.types";
|
} from "@/contracts/types/podcast.types";
|
||||||
import { podcastsApiService } from "@/lib/apis/podcasts-api.service";
|
import { podcastsApiService } from "@/lib/apis/podcasts-api.service";
|
||||||
|
import { getBearerToken } from "@/lib/auth-utils";
|
||||||
import { cacheKeys } from "@/lib/query-client/cache-keys";
|
import { cacheKeys } from "@/lib/query-client/cache-keys";
|
||||||
import { queryClient } from "@/lib/query-client/client";
|
import { queryClient } from "@/lib/query-client/client";
|
||||||
import { globalPodcastsQueryParamsAtom } from "./ui.atoms";
|
import { globalPodcastsQueryParamsAtom } from "./ui.atoms";
|
||||||
|
|
||||||
export const deletePodcastMutationAtom = atomWithMutation((get) => {
|
export const deletePodcastMutationAtom = atomWithMutation((get) => {
|
||||||
const searchSpaceId = get(activeSearchSpaceIdAtom);
|
const searchSpaceId = get(activeSearchSpaceIdAtom);
|
||||||
const authToken = localStorage.getItem("surfsense_bearer_token");
|
const authToken = getBearerToken();
|
||||||
const podcastsQueryParams = get(globalPodcastsQueryParamsAtom);
|
const podcastsQueryParams = get(globalPodcastsQueryParamsAtom);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -37,7 +38,7 @@ export const deletePodcastMutationAtom = atomWithMutation((get) => {
|
||||||
|
|
||||||
export const generatePodcastMutationAtom = atomWithMutation((get) => {
|
export const generatePodcastMutationAtom = atomWithMutation((get) => {
|
||||||
const searchSpaceId = get(activeSearchSpaceIdAtom);
|
const searchSpaceId = get(activeSearchSpaceIdAtom);
|
||||||
const authToken = localStorage.getItem("surfsense_bearer_token");
|
const authToken = getBearerToken();
|
||||||
const podcastsQueryParams = get(globalPodcastsQueryParamsAtom);
|
const podcastsQueryParams = get(globalPodcastsQueryParamsAtom);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -2,22 +2,25 @@
|
||||||
|
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
import { getAndClearRedirectPath, setBearerToken } from "@/lib/auth-utils";
|
||||||
|
|
||||||
interface TokenHandlerProps {
|
interface TokenHandlerProps {
|
||||||
redirectPath?: string; // Path to redirect after storing token
|
redirectPath?: string; // Default path to redirect after storing token (if no saved path)
|
||||||
tokenParamName?: string; // Name of the URL parameter containing the token
|
tokenParamName?: string; // Name of the URL parameter containing the token
|
||||||
storageKey?: string; // Key to use when storing in localStorage
|
storageKey?: string; // Key to use when storing in localStorage (kept for backwards compatibility)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Client component that extracts a token from URL parameters and stores it in localStorage
|
* Client component that extracts a token from URL parameters and stores it in localStorage
|
||||||
|
* After storing the token, it redirects the user back to the page they were on before
|
||||||
|
* being redirected to login (if available), or to the default redirectPath.
|
||||||
*
|
*
|
||||||
* @param redirectPath - Path to redirect after storing token (default: '/')
|
* @param redirectPath - Default path to redirect after storing token (default: '/dashboard')
|
||||||
* @param tokenParamName - Name of the URL parameter containing the token (default: 'token')
|
* @param tokenParamName - Name of the URL parameter containing the token (default: 'token')
|
||||||
* @param storageKey - Key to use when storing in localStorage (default: 'auth_token')
|
* @param storageKey - Key to use when storing in localStorage (default: 'surfsense_bearer_token')
|
||||||
*/
|
*/
|
||||||
const TokenHandler = ({
|
const TokenHandler = ({
|
||||||
redirectPath = "/",
|
redirectPath = "/dashboard",
|
||||||
tokenParamName = "token",
|
tokenParamName = "token",
|
||||||
storageKey = "surfsense_bearer_token",
|
storageKey = "surfsense_bearer_token",
|
||||||
}: TokenHandlerProps) => {
|
}: TokenHandlerProps) => {
|
||||||
|
|
@ -33,14 +36,22 @@ const TokenHandler = ({
|
||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
try {
|
try {
|
||||||
// Store token in localStorage
|
// Store token in localStorage using both methods for compatibility
|
||||||
localStorage.setItem(storageKey, token);
|
localStorage.setItem(storageKey, token);
|
||||||
// console.log(`Token stored in localStorage with key: ${storageKey}`);
|
setBearerToken(token);
|
||||||
|
|
||||||
// Redirect to specified path
|
// Check if there's a saved redirect path from before the auth flow
|
||||||
router.push(redirectPath);
|
const savedRedirectPath = getAndClearRedirectPath();
|
||||||
|
|
||||||
|
// Use the saved path if available, otherwise use the default redirectPath
|
||||||
|
const finalRedirectPath = savedRedirectPath || redirectPath;
|
||||||
|
|
||||||
|
// Redirect to the appropriate path
|
||||||
|
router.push(finalRedirectPath);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error storing token in localStorage:", error);
|
console.error("Error storing token in localStorage:", error);
|
||||||
|
// Even if there's an error, try to redirect to the default path
|
||||||
|
router.push(redirectPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [searchParams, tokenParamName, storageKey, redirectPath, router]);
|
}, [searchParams, tokenParamName, storageKey, redirectPath, router]);
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import {
|
||||||
BreadcrumbSeparator,
|
BreadcrumbSeparator,
|
||||||
} from "@/components/ui/breadcrumb";
|
} from "@/components/ui/breadcrumb";
|
||||||
import { useSearchSpace } from "@/hooks/use-search-space";
|
import { useSearchSpace } from "@/hooks/use-search-space";
|
||||||
|
import { authenticatedFetch, getBearerToken } from "@/lib/auth-utils";
|
||||||
|
|
||||||
interface BreadcrumbItemInterface {
|
interface BreadcrumbItemInterface {
|
||||||
label: string;
|
label: string;
|
||||||
|
|
@ -41,17 +42,12 @@ export function DashboardBreadcrumb() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (segments[2] === "editor" && segments[3] && searchSpaceId) {
|
if (segments[2] === "editor" && segments[3] && searchSpaceId) {
|
||||||
const documentId = segments[3];
|
const documentId = segments[3];
|
||||||
const token =
|
const token = getBearerToken();
|
||||||
typeof window !== "undefined" ? localStorage.getItem("surfsense_bearer_token") : null;
|
|
||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
fetch(
|
authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/documents/${documentId}/editor-content`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/documents/${documentId}/editor-content`,
|
||||||
{
|
{ method: "GET" }
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import { Switch } from "@/components/ui/switch";
|
||||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { type CommunityPrompt, useCommunityPrompts } from "@/hooks/use-community-prompts";
|
import { type CommunityPrompt, useCommunityPrompts } from "@/hooks/use-community-prompts";
|
||||||
|
import { authenticatedFetch } from "@/lib/auth-utils";
|
||||||
|
|
||||||
interface SetupPromptStepProps {
|
interface SetupPromptStepProps {
|
||||||
searchSpaceId: number;
|
searchSpaceId: number;
|
||||||
|
|
@ -74,14 +75,11 @@ export function SetupPromptStep({ searchSpaceId, onComplete }: SetupPromptStepPr
|
||||||
|
|
||||||
// Only send update if there's something to update
|
// Only send update if there's something to update
|
||||||
if (Object.keys(payload).length > 0) {
|
if (Object.keys(payload).length > 0) {
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}`,
|
||||||
{
|
{
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: {
|
headers: { "Content-Type": "application/json" },
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify(payload),
|
body: JSON.stringify(payload),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { type CommunityPrompt, useCommunityPrompts } from "@/hooks/use-community-prompts";
|
import { type CommunityPrompt, useCommunityPrompts } from "@/hooks/use-community-prompts";
|
||||||
import { useSearchSpace } from "@/hooks/use-search-space";
|
import { useSearchSpace } from "@/hooks/use-search-space";
|
||||||
|
import { authenticatedFetch } from "@/lib/auth-utils";
|
||||||
|
|
||||||
interface PromptConfigManagerProps {
|
interface PromptConfigManagerProps {
|
||||||
searchSpaceId: number;
|
searchSpaceId: number;
|
||||||
|
|
@ -78,14 +79,11 @@ export function PromptConfigManager({ searchSpaceId }: PromptConfigManagerProps)
|
||||||
|
|
||||||
// Only send request if we have something to update
|
// Only send request if we have something to update
|
||||||
if (Object.keys(payload).length > 0) {
|
if (Object.keys(payload).length > 0) {
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}`,
|
||||||
{
|
{
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: {
|
headers: { "Content-Type": "application/json" },
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify(payload),
|
body: JSON.stringify(payload),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Progress } from "@/components/ui/progress";
|
import { Progress } from "@/components/ui/progress";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
import { getAuthHeaders } from "@/lib/auth-utils";
|
||||||
import { GridPattern } from "./GridPattern";
|
import { GridPattern } from "./GridPattern";
|
||||||
|
|
||||||
interface DocumentUploadTabProps {
|
interface DocumentUploadTabProps {
|
||||||
|
|
@ -168,9 +169,7 @@ export function DocumentUploadTab({ searchSpaceId }: DocumentUploadTabProps) {
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/documents/fileupload`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/documents/fileupload`,
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: getAuthHeaders(),
|
||||||
Authorization: `Bearer ${window.localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
body: formData,
|
body: formData,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import {
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { authenticatedFetch } from "@/lib/auth-utils";
|
||||||
|
|
||||||
const youtubeRegex =
|
const youtubeRegex =
|
||||||
/^(https:\/\/)?(www\.)?(youtube\.com\/watch\?v=|youtu\.be\/)([a-zA-Z0-9_-]{11})$/;
|
/^(https:\/\/)?(www\.)?(youtube\.com\/watch\?v=|youtu\.be\/)([a-zA-Z0-9_-]{11})$/;
|
||||||
|
|
@ -66,14 +67,11 @@ export function YouTubeTab({ searchSpaceId }: YouTubeTabProps) {
|
||||||
|
|
||||||
const videoUrls = videoTags.map((tag) => tag.text);
|
const videoUrls = videoTags.map((tag) => tag.text);
|
||||||
|
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/documents`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/documents`,
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: { "Content-Type": "application/json" },
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
document_type: "YOUTUBE_VIDEO",
|
document_type: "YOUTUBE_VIDEO",
|
||||||
content: videoUrls,
|
content: videoUrls,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import { getBearerToken } from "@/lib/auth-utils";
|
||||||
|
|
||||||
interface UseApiKeyReturn {
|
interface UseApiKeyReturn {
|
||||||
apiKey: string | null;
|
apiKey: string | null;
|
||||||
|
|
@ -17,7 +18,7 @@ export function useApiKey(): UseApiKeyReturn {
|
||||||
// Load API key from localStorage
|
// Load API key from localStorage
|
||||||
const loadApiKey = () => {
|
const loadApiKey = () => {
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem("surfsense_bearer_token");
|
const token = getBearerToken();
|
||||||
setApiKey(token);
|
setApiKey(token);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading API key:", error);
|
console.error("Error loading API key:", error);
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { useCallback, useEffect, useState } from "react";
|
||||||
import type { ChatDetails } from "@/app/dashboard/[search_space_id]/chats/chats-client";
|
import type { ChatDetails } from "@/app/dashboard/[search_space_id]/chats/chats-client";
|
||||||
import type { ResearchMode } from "@/components/chat";
|
import type { ResearchMode } from "@/components/chat";
|
||||||
import type { Document } from "@/hooks/use-documents";
|
import type { Document } from "@/hooks/use-documents";
|
||||||
|
import { getBearerToken } from "@/lib/auth-utils";
|
||||||
|
|
||||||
interface UseChatStateProps {
|
interface UseChatStateProps {
|
||||||
search_space_id: string;
|
search_space_id: string;
|
||||||
|
|
@ -22,7 +23,7 @@ export function useChatState({ chat_id }: UseChatStateProps) {
|
||||||
const [topK, setTopK] = useState<number>(5);
|
const [topK, setTopK] = useState<number>(5);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const bearerToken = localStorage.getItem("surfsense_bearer_token");
|
const bearerToken = getBearerToken();
|
||||||
setToken(bearerToken);
|
setToken(bearerToken);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import {
|
||||||
type SearchSourceConnector,
|
type SearchSourceConnector,
|
||||||
useSearchSourceConnectors,
|
useSearchSourceConnectors,
|
||||||
} from "@/hooks/use-search-source-connectors";
|
} from "@/hooks/use-search-source-connectors";
|
||||||
|
import { authenticatedFetch } from "@/lib/auth-utils";
|
||||||
|
|
||||||
const normalizeListInput = (value: unknown): string[] => {
|
const normalizeListInput = (value: unknown): string[] => {
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
|
|
@ -178,16 +179,11 @@ export function useConnectorEditPage(connectorId: number, searchSpaceId: string)
|
||||||
setIsFetchingRepos(true);
|
setIsFetchingRepos(true);
|
||||||
setFetchedRepos(null);
|
setFetchedRepos(null);
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem("surfsense_bearer_token");
|
const response = await authenticatedFetch(
|
||||||
if (!token) throw new Error("No auth token");
|
|
||||||
const response = await fetch(
|
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/github/repositories`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/github/repositories`,
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: { "Content-Type": "application/json" },
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ github_pat: values.github_pat }),
|
body: JSON.stringify({ github_pat: values.github_pat }),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { authenticatedFetch } from "@/lib/auth-utils";
|
||||||
|
|
||||||
// Types for connector API
|
// Types for connector API
|
||||||
export interface ConnectorConfig {
|
export interface ConnectorConfig {
|
||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
|
|
@ -32,14 +34,11 @@ export const getConnectorTypeDisplay = (type: string): string => {
|
||||||
export const ConnectorService = {
|
export const ConnectorService = {
|
||||||
// Create a new connector
|
// Create a new connector
|
||||||
async createConnector(data: CreateConnectorRequest): Promise<Connector> {
|
async createConnector(data: CreateConnectorRequest): Promise<Connector> {
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-source-connectors`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-source-connectors`,
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: { "Content-Type": "application/json" },
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
@ -54,13 +53,9 @@ export const ConnectorService = {
|
||||||
|
|
||||||
// Get all connectors
|
// Get all connectors
|
||||||
async getConnectors(skip = 0, limit = 100): Promise<Connector[]> {
|
async getConnectors(skip = 0, limit = 100): Promise<Connector[]> {
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-source-connectors?skip=${skip}&limit=${limit}`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-source-connectors?skip=${skip}&limit=${limit}`,
|
||||||
{
|
{ method: "GET" }
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
@ -73,13 +68,9 @@ export const ConnectorService = {
|
||||||
|
|
||||||
// Get a specific connector
|
// Get a specific connector
|
||||||
async getConnector(connectorId: number): Promise<Connector> {
|
async getConnector(connectorId: number): Promise<Connector> {
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-source-connectors/${connectorId}`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-source-connectors/${connectorId}`,
|
||||||
{
|
{ method: "GET" }
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
@ -92,14 +83,11 @@ export const ConnectorService = {
|
||||||
|
|
||||||
// Update a connector
|
// Update a connector
|
||||||
async updateConnector(connectorId: number, data: CreateConnectorRequest): Promise<Connector> {
|
async updateConnector(connectorId: number, data: CreateConnectorRequest): Promise<Connector> {
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-source-connectors/${connectorId}`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-source-connectors/${connectorId}`,
|
||||||
{
|
{
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: {
|
headers: { "Content-Type": "application/json" },
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
@ -114,14 +102,9 @@ export const ConnectorService = {
|
||||||
|
|
||||||
// Delete a connector
|
// Delete a connector
|
||||||
async deleteConnector(connectorId: number): Promise<void> {
|
async deleteConnector(connectorId: number): Promise<void> {
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-source-connectors/${connectorId}`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-source-connectors/${connectorId}`,
|
||||||
{
|
{ method: "DELETE" }
|
||||||
method: "DELETE",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import { authenticatedFetch } from "@/lib/auth-utils";
|
||||||
|
|
||||||
export interface Chunk {
|
export interface Chunk {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
@ -49,13 +50,10 @@ export function useDocumentByChunk() {
|
||||||
setError(null);
|
setError(null);
|
||||||
setDocument(null);
|
setDocument(null);
|
||||||
|
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/documents/by-chunk/${chunkId}`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/documents/by-chunk/${chunkId}`,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: { "Content-Type": "application/json" },
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
import { authenticatedFetch } from "@/lib/auth-utils";
|
||||||
|
|
||||||
export interface DocumentTypeCount {
|
export interface DocumentTypeCount {
|
||||||
type: string;
|
type: string;
|
||||||
|
|
@ -23,11 +24,6 @@ export const useDocumentTypes = (searchSpaceId?: number, lazy: boolean = false)
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
const token = localStorage.getItem("surfsense_bearer_token");
|
|
||||||
|
|
||||||
if (!token) {
|
|
||||||
throw new Error("No authentication token found");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build URL with optional search_space_id query parameter
|
// Build URL with optional search_space_id query parameter
|
||||||
const url = new URL(
|
const url = new URL(
|
||||||
|
|
@ -37,12 +33,9 @@ export const useDocumentTypes = (searchSpaceId?: number, lazy: boolean = false)
|
||||||
url.searchParams.append("search_space_id", spaceId.toString());
|
url.searchParams.append("search_space_id", spaceId.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(url.toString(), {
|
const response = await authenticatedFetch(url.toString(), {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: { "Content-Type": "application/json" },
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import { authenticatedFetch } from "@/lib/auth-utils";
|
||||||
import { normalizeListResponse } from "@/lib/pagination";
|
import { normalizeListResponse } from "@/lib/pagination";
|
||||||
|
|
||||||
export interface Document {
|
export interface Document {
|
||||||
|
|
@ -78,14 +79,9 @@ export function useDocuments(searchSpaceId: number, options?: UseDocumentsOption
|
||||||
params.append("document_types", effectiveDocumentTypes.join(","));
|
params.append("document_types", effectiveDocumentTypes.join(","));
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/documents?${params.toString()}`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/documents?${params.toString()}`,
|
||||||
{
|
{ method: "GET" }
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
method: "GET",
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
@ -159,14 +155,9 @@ export function useDocuments(searchSpaceId: number, options?: UseDocumentsOption
|
||||||
params.append("document_types", effectiveDocumentTypes.join(","));
|
params.append("document_types", effectiveDocumentTypes.join(","));
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/documents/search?${params.toString()}`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/documents/search?${params.toString()}`,
|
||||||
{
|
{ method: "GET" }
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
method: "GET",
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
@ -193,14 +184,9 @@ export function useDocuments(searchSpaceId: number, options?: UseDocumentsOption
|
||||||
const deleteDocument = useCallback(
|
const deleteDocument = useCallback(
|
||||||
async (documentId: number) => {
|
async (documentId: number) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/documents/${documentId}`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/documents/${documentId}`,
|
||||||
{
|
{ method: "DELETE" }
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
method: "DELETE",
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
@ -228,14 +214,9 @@ export function useDocuments(searchSpaceId: number, options?: UseDocumentsOption
|
||||||
search_space_id: searchSpaceId.toString(),
|
search_space_id: searchSpaceId.toString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/documents/type-counts?${params.toString()}`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/documents/type-counts?${params.toString()}`,
|
||||||
{
|
{ method: "GET" }
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
method: "GET",
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import { authenticatedFetch } from "@/lib/auth-utils";
|
||||||
|
|
||||||
export interface LLMConfig {
|
export interface LLMConfig {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
@ -61,14 +62,9 @@ export function useLLMConfigs(searchSpaceId: number | null) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/llm-configs?search_space_id=${searchSpaceId}`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/llm-configs?search_space_id=${searchSpaceId}`,
|
||||||
{
|
{ method: "GET" }
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
method: "GET",
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
@ -92,14 +88,11 @@ export function useLLMConfigs(searchSpaceId: number | null) {
|
||||||
|
|
||||||
const createLLMConfig = async (config: CreateLLMConfig): Promise<LLMConfig | null> => {
|
const createLLMConfig = async (config: CreateLLMConfig): Promise<LLMConfig | null> => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/llm-configs`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/llm-configs`,
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: { "Content-Type": "application/json" },
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify(config),
|
body: JSON.stringify(config),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
@ -122,14 +115,9 @@ export function useLLMConfigs(searchSpaceId: number | null) {
|
||||||
|
|
||||||
const deleteLLMConfig = async (id: number): Promise<boolean> => {
|
const deleteLLMConfig = async (id: number): Promise<boolean> => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/llm-configs/${id}`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/llm-configs/${id}`,
|
||||||
{
|
{ method: "DELETE" }
|
||||||
method: "DELETE",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
@ -151,14 +139,11 @@ export function useLLMConfigs(searchSpaceId: number | null) {
|
||||||
config: UpdateLLMConfig
|
config: UpdateLLMConfig
|
||||||
): Promise<LLMConfig | null> => {
|
): Promise<LLMConfig | null> => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/llm-configs/${id}`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/llm-configs/${id}`,
|
||||||
{
|
{
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: {
|
headers: { "Content-Type": "application/json" },
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify(config),
|
body: JSON.stringify(config),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
@ -203,14 +188,9 @@ export function useLLMPreferences(searchSpaceId: number | null) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/llm-preferences`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/llm-preferences`,
|
||||||
{
|
{ method: "GET" }
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
method: "GET",
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
@ -239,14 +219,11 @@ export function useLLMPreferences(searchSpaceId: number | null) {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/llm-preferences`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/llm-preferences`,
|
||||||
{
|
{
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: {
|
headers: { "Content-Type": "application/json" },
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify(newPreferences),
|
body: JSON.stringify(newPreferences),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
@ -293,14 +270,9 @@ export function useGlobalLLMConfigs() {
|
||||||
const fetchGlobalConfigs = async () => {
|
const fetchGlobalConfigs = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/global-llm-configs`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/global-llm-configs`,
|
||||||
{
|
{ method: "GET" }
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
method: "GET",
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import { authenticatedFetch } from "@/lib/auth-utils";
|
||||||
|
|
||||||
export type LogLevel = "DEBUG" | "INFO" | "WARNING" | "ERROR" | "CRITICAL";
|
export type LogLevel = "DEBUG" | "INFO" | "WARNING" | "ERROR" | "CRITICAL";
|
||||||
export type LogStatus = "IN_PROGRESS" | "SUCCESS" | "FAILED";
|
export type LogStatus = "IN_PROGRESS" | "SUCCESS" | "FAILED";
|
||||||
|
|
@ -95,14 +96,9 @@ export function useLogs(searchSpaceId?: number, filters: LogFilters = {}) {
|
||||||
if (options.skip !== undefined) params.append("skip", options.skip.toString());
|
if (options.skip !== undefined) params.append("skip", options.skip.toString());
|
||||||
if (options.limit !== undefined) params.append("limit", options.limit.toString());
|
if (options.limit !== undefined) params.append("limit", options.limit.toString());
|
||||||
|
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/logs?${params}`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/logs?${params}`,
|
||||||
{
|
{ method: "GET" }
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
method: "GET",
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
@ -147,14 +143,14 @@ export function useLogs(searchSpaceId?: number, filters: LogFilters = {}) {
|
||||||
// Function to create a new log
|
// Function to create a new log
|
||||||
const createLog = useCallback(async (logData: Omit<Log, "id" | "created_at">) => {
|
const createLog = useCallback(async (logData: Omit<Log, "id" | "created_at">) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/logs`, {
|
const response = await authenticatedFetch(
|
||||||
headers: {
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/logs`,
|
||||||
"Content-Type": "application/json",
|
{
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
headers: { "Content-Type": "application/json" },
|
||||||
},
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify(logData),
|
body: JSON.stringify(logData),
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorData = await response.json().catch(() => ({}));
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
|
@ -179,13 +175,10 @@ export function useLogs(searchSpaceId?: number, filters: LogFilters = {}) {
|
||||||
updateData: Partial<Omit<Log, "id" | "created_at" | "search_space_id">>
|
updateData: Partial<Omit<Log, "id" | "created_at" | "search_space_id">>
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/logs/${logId}`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/logs/${logId}`,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: { "Content-Type": "application/json" },
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
body: JSON.stringify(updateData),
|
body: JSON.stringify(updateData),
|
||||||
}
|
}
|
||||||
|
|
@ -212,14 +205,9 @@ export function useLogs(searchSpaceId?: number, filters: LogFilters = {}) {
|
||||||
// Function to delete a log
|
// Function to delete a log
|
||||||
const deleteLog = useCallback(async (logId: number) => {
|
const deleteLog = useCallback(async (logId: number) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/logs/${logId}`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/logs/${logId}`,
|
||||||
{
|
{ method: "DELETE" }
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
method: "DELETE",
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
@ -240,14 +228,9 @@ export function useLogs(searchSpaceId?: number, filters: LogFilters = {}) {
|
||||||
// Function to get a single log
|
// Function to get a single log
|
||||||
const getLog = useCallback(async (logId: number) => {
|
const getLog = useCallback(async (logId: number) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/logs/${logId}`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/logs/${logId}`,
|
||||||
{
|
{ method: "GET" }
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
method: "GET",
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
@ -287,14 +270,9 @@ export function useLogsSummary(searchSpaceId: number, hours: number = 24) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/logs/search-space/${searchSpaceId}/summary?hours=${hours}`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/logs/search-space/${searchSpaceId}/summary?hours=${hours}`,
|
||||||
{
|
{ method: "GET" }
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
method: "GET",
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import { authenticatedFetch, getBearerToken, handleUnauthorized } from "@/lib/auth-utils";
|
||||||
|
|
||||||
// ============ Types ============
|
// ============ Types ============
|
||||||
|
|
||||||
|
|
@ -105,22 +106,11 @@ export function useMembers(searchSpaceId: number) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}/members`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}/members`,
|
||||||
{
|
{ method: "GET" }
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
method: "GET",
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.status === 401) {
|
|
||||||
localStorage.removeItem("surfsense_bearer_token");
|
|
||||||
window.location.href = "/";
|
|
||||||
throw new Error("Unauthorized");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorData = await response.json().catch(() => ({}));
|
const errorData = await response.json().catch(() => ({}));
|
||||||
throw new Error(errorData.detail || "Failed to fetch members");
|
throw new Error(errorData.detail || "Failed to fetch members");
|
||||||
|
|
@ -145,13 +135,10 @@ export function useMembers(searchSpaceId: number) {
|
||||||
const updateMemberRole = useCallback(
|
const updateMemberRole = useCallback(
|
||||||
async (membershipId: number, roleId: number | null) => {
|
async (membershipId: number, roleId: number | null) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}/members/${membershipId}`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}/members/${membershipId}`,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: { "Content-Type": "application/json" },
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
body: JSON.stringify({ role_id: roleId }),
|
body: JSON.stringify({ role_id: roleId }),
|
||||||
}
|
}
|
||||||
|
|
@ -177,14 +164,9 @@ export function useMembers(searchSpaceId: number) {
|
||||||
const removeMember = useCallback(
|
const removeMember = useCallback(
|
||||||
async (membershipId: number) => {
|
async (membershipId: number) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}/members/${membershipId}`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}/members/${membershipId}`,
|
||||||
{
|
{ method: "DELETE" }
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
method: "DELETE",
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
@ -205,14 +187,9 @@ export function useMembers(searchSpaceId: number) {
|
||||||
|
|
||||||
const leaveSearchSpace = useCallback(async () => {
|
const leaveSearchSpace = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}/members/me`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}/members/me`,
|
||||||
{
|
{ method: "DELETE" }
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
method: "DELETE",
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
@ -251,22 +228,11 @@ export function useRoles(searchSpaceId: number) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}/roles`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}/roles`,
|
||||||
{
|
{ method: "GET" }
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
method: "GET",
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.status === 401) {
|
|
||||||
localStorage.removeItem("surfsense_bearer_token");
|
|
||||||
window.location.href = "/";
|
|
||||||
throw new Error("Unauthorized");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorData = await response.json().catch(() => ({}));
|
const errorData = await response.json().catch(() => ({}));
|
||||||
throw new Error(errorData.detail || "Failed to fetch roles");
|
throw new Error(errorData.detail || "Failed to fetch roles");
|
||||||
|
|
@ -291,13 +257,10 @@ export function useRoles(searchSpaceId: number) {
|
||||||
const createRole = useCallback(
|
const createRole = useCallback(
|
||||||
async (roleData: RoleCreate) => {
|
async (roleData: RoleCreate) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}/roles`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}/roles`,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: { "Content-Type": "application/json" },
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify(roleData),
|
body: JSON.stringify(roleData),
|
||||||
}
|
}
|
||||||
|
|
@ -323,13 +286,10 @@ export function useRoles(searchSpaceId: number) {
|
||||||
const updateRole = useCallback(
|
const updateRole = useCallback(
|
||||||
async (roleId: number, roleData: RoleUpdate) => {
|
async (roleId: number, roleData: RoleUpdate) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}/roles/${roleId}`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}/roles/${roleId}`,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: { "Content-Type": "application/json" },
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
body: JSON.stringify(roleData),
|
body: JSON.stringify(roleData),
|
||||||
}
|
}
|
||||||
|
|
@ -355,14 +315,9 @@ export function useRoles(searchSpaceId: number) {
|
||||||
const deleteRole = useCallback(
|
const deleteRole = useCallback(
|
||||||
async (roleId: number) => {
|
async (roleId: number) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}/roles/${roleId}`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}/roles/${roleId}`,
|
||||||
{
|
{ method: "DELETE" }
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
method: "DELETE",
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
@ -404,22 +359,11 @@ export function useInvites(searchSpaceId: number) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}/invites`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}/invites`,
|
||||||
{
|
{ method: "GET" }
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
method: "GET",
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.status === 401) {
|
|
||||||
localStorage.removeItem("surfsense_bearer_token");
|
|
||||||
window.location.href = "/";
|
|
||||||
throw new Error("Unauthorized");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorData = await response.json().catch(() => ({}));
|
const errorData = await response.json().catch(() => ({}));
|
||||||
throw new Error(errorData.detail || "Failed to fetch invites");
|
throw new Error(errorData.detail || "Failed to fetch invites");
|
||||||
|
|
@ -444,13 +388,10 @@ export function useInvites(searchSpaceId: number) {
|
||||||
const createInvite = useCallback(
|
const createInvite = useCallback(
|
||||||
async (inviteData: InviteCreate) => {
|
async (inviteData: InviteCreate) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}/invites`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}/invites`,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: { "Content-Type": "application/json" },
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify(inviteData),
|
body: JSON.stringify(inviteData),
|
||||||
}
|
}
|
||||||
|
|
@ -476,13 +417,10 @@ export function useInvites(searchSpaceId: number) {
|
||||||
const updateInvite = useCallback(
|
const updateInvite = useCallback(
|
||||||
async (inviteId: number, inviteData: InviteUpdate) => {
|
async (inviteId: number, inviteData: InviteUpdate) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}/invites/${inviteId}`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}/invites/${inviteId}`,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: { "Content-Type": "application/json" },
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
body: JSON.stringify(inviteData),
|
body: JSON.stringify(inviteData),
|
||||||
}
|
}
|
||||||
|
|
@ -508,14 +446,9 @@ export function useInvites(searchSpaceId: number) {
|
||||||
const revokeInvite = useCallback(
|
const revokeInvite = useCallback(
|
||||||
async (inviteId: number) => {
|
async (inviteId: number) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}/invites/${inviteId}`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}/invites/${inviteId}`,
|
||||||
{
|
{ method: "DELETE" }
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
method: "DELETE",
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
@ -555,14 +488,9 @@ export function usePermissions() {
|
||||||
const fetchPermissions = useCallback(async () => {
|
const fetchPermissions = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/permissions`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/permissions`,
|
||||||
{
|
{ method: "GET" }
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
method: "GET",
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
@ -619,22 +547,11 @@ export function useUserAccess(searchSpaceId: number) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}/my-access`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}/my-access`,
|
||||||
{
|
{ method: "GET" }
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
method: "GET",
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.status === 401) {
|
|
||||||
localStorage.removeItem("surfsense_bearer_token");
|
|
||||||
window.location.href = "/";
|
|
||||||
throw new Error("Unauthorized");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorData = await response.json().catch(() => ({}));
|
const errorData = await response.json().catch(() => ({}));
|
||||||
throw new Error(errorData.detail || "Failed to fetch access info");
|
throw new Error(errorData.detail || "Failed to fetch access info");
|
||||||
|
|
@ -737,13 +654,10 @@ export function useInviteInfo(inviteCode: string | null) {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/invites/accept`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/invites/accept`,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: { "Content-Type": "application/json" },
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({ invite_code: inviteCode }),
|
body: JSON.stringify({ invite_code: inviteCode }),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
import { authenticatedFetch, getBearerToken, handleUnauthorized } from "@/lib/auth-utils";
|
||||||
|
|
||||||
export interface SearchSourceConnector {
|
export interface SearchSourceConnector {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
@ -66,11 +67,6 @@ export const useSearchSourceConnectors = (lazy: boolean = false, searchSpaceId?:
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
const token = localStorage.getItem("surfsense_bearer_token");
|
|
||||||
|
|
||||||
if (!token) {
|
|
||||||
throw new Error("No authentication token found");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build URL with optional search_space_id query parameter
|
// Build URL with optional search_space_id query parameter
|
||||||
const url = new URL(
|
const url = new URL(
|
||||||
|
|
@ -80,12 +76,9 @@ export const useSearchSourceConnectors = (lazy: boolean = false, searchSpaceId?:
|
||||||
url.searchParams.append("search_space_id", spaceId.toString());
|
url.searchParams.append("search_space_id", spaceId.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(url.toString(), {
|
const response = await authenticatedFetch(url.toString(), {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: { "Content-Type": "application/json" },
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
@ -176,24 +169,15 @@ export const useSearchSourceConnectors = (lazy: boolean = false, searchSpaceId?:
|
||||||
spaceId: number
|
spaceId: number
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem("surfsense_bearer_token");
|
|
||||||
|
|
||||||
if (!token) {
|
|
||||||
throw new Error("No authentication token found");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add search_space_id as a query parameter
|
// Add search_space_id as a query parameter
|
||||||
const url = new URL(
|
const url = new URL(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-source-connectors`
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-source-connectors`
|
||||||
);
|
);
|
||||||
url.searchParams.append("search_space_id", spaceId.toString());
|
url.searchParams.append("search_space_id", spaceId.toString());
|
||||||
|
|
||||||
const response = await fetch(url.toString(), {
|
const response = await authenticatedFetch(url.toString(), {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: { "Content-Type": "application/json" },
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify(connectorData),
|
body: JSON.stringify(connectorData),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -222,20 +206,11 @@ export const useSearchSourceConnectors = (lazy: boolean = false, searchSpaceId?:
|
||||||
>
|
>
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem("surfsense_bearer_token");
|
const response = await authenticatedFetch(
|
||||||
|
|
||||||
if (!token) {
|
|
||||||
throw new Error("No authentication token found");
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(
|
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-source-connectors/${connectorId}`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-source-connectors/${connectorId}`,
|
||||||
{
|
{
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: {
|
headers: { "Content-Type": "application/json" },
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify(connectorData),
|
body: JSON.stringify(connectorData),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
@ -262,20 +237,11 @@ export const useSearchSourceConnectors = (lazy: boolean = false, searchSpaceId?:
|
||||||
*/
|
*/
|
||||||
const deleteConnector = async (connectorId: number) => {
|
const deleteConnector = async (connectorId: number) => {
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem("surfsense_bearer_token");
|
const response = await authenticatedFetch(
|
||||||
|
|
||||||
if (!token) {
|
|
||||||
throw new Error("No authentication token found");
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(
|
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-source-connectors/${connectorId}`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-source-connectors/${connectorId}`,
|
||||||
{
|
{
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
headers: {
|
headers: { "Content-Type": "application/json" },
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -302,12 +268,6 @@ export const useSearchSourceConnectors = (lazy: boolean = false, searchSpaceId?:
|
||||||
endDate?: string
|
endDate?: string
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem("surfsense_bearer_token");
|
|
||||||
|
|
||||||
if (!token) {
|
|
||||||
throw new Error("No authentication token found");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build query parameters
|
// Build query parameters
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
search_space_id: searchSpaceId.toString(),
|
search_space_id: searchSpaceId.toString(),
|
||||||
|
|
@ -319,16 +279,13 @@ export const useSearchSourceConnectors = (lazy: boolean = false, searchSpaceId?:
|
||||||
params.append("end_date", endDate);
|
params.append("end_date", endDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${
|
`${
|
||||||
process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL
|
process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL
|
||||||
}/api/v1/search-source-connectors/${connectorId}/index?${params.toString()}`,
|
}/api/v1/search-source-connectors/${connectorId}/index?${params.toString()}`,
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: { "Content-Type": "application/json" },
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import { authenticatedFetch } from "@/lib/auth-utils";
|
||||||
|
|
||||||
interface SearchSpace {
|
interface SearchSpace {
|
||||||
created_at: string;
|
created_at: string;
|
||||||
|
|
@ -29,23 +30,11 @@ export function useSearchSpace({ searchSpaceId, autoFetch = true }: UseSearchSpa
|
||||||
if (typeof window === "undefined") return;
|
if (typeof window === "undefined") return;
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}`,
|
||||||
{
|
{ method: "GET" }
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
method: "GET",
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.status === 401) {
|
|
||||||
// Clear token and redirect to home
|
|
||||||
localStorage.removeItem("surfsense_bearer_token");
|
|
||||||
window.location.href = "/";
|
|
||||||
throw new Error("Unauthorized: Redirecting to login page");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Failed to fetch search space: ${response.status}`);
|
throw new Error(`Failed to fetch search space: ${response.status}`);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import { authenticatedFetch } from "@/lib/auth-utils";
|
||||||
|
|
||||||
interface SearchSpace {
|
interface SearchSpace {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
@ -23,19 +24,14 @@ export function useSearchSpaces() {
|
||||||
const fetchSearchSpaces = async () => {
|
const fetchSearchSpaces = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces`,
|
||||||
{
|
{ method: "GET" }
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
method: "GET",
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
toast.error("Not authenticated");
|
toast.error("Failed to fetch search spaces");
|
||||||
throw new Error("Not authenticated");
|
throw new Error("Failed to fetch search spaces");
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
@ -56,19 +52,14 @@ export function useSearchSpaces() {
|
||||||
const refreshSearchSpaces = async () => {
|
const refreshSearchSpaces = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await authenticatedFetch(
|
||||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces`,
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces`,
|
||||||
{
|
{ method: "GET" }
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
|
||||||
},
|
|
||||||
method: "GET",
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
toast.error("Not authenticated");
|
toast.error("Failed to fetch search spaces");
|
||||||
throw new Error("Not authenticated");
|
throw new Error("Failed to fetch search spaces");
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import { authenticatedFetch } from "@/lib/auth-utils";
|
||||||
|
|
||||||
interface User {
|
interface User {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -25,19 +26,10 @@ export function useUser() {
|
||||||
if (typeof window === "undefined") return;
|
if (typeof window === "undefined") return;
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response = await fetch(`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/users/me`, {
|
const response = await authenticatedFetch(
|
||||||
headers: {
|
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/users/me`,
|
||||||
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
|
{ method: "GET" }
|
||||||
},
|
);
|
||||||
method: "GET",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.status === 401) {
|
|
||||||
// Clear token and redirect to home
|
|
||||||
localStorage.removeItem("surfsense_bearer_token");
|
|
||||||
window.location.href = "/";
|
|
||||||
throw new Error("Unauthorized: Redirecting to login page");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Failed to fetch user: ${response.status}`);
|
throw new Error(`Failed to fetch user: ${response.status}`);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { th } from "date-fns/locale";
|
|
||||||
import type z from "zod";
|
import type z from "zod";
|
||||||
|
import { getBearerToken, handleUnauthorized } from "../auth-utils";
|
||||||
import { AppError, AuthenticationError, AuthorizationError, NotFoundError } from "../error";
|
import { AppError, AuthenticationError, AuthorizationError, NotFoundError } from "../error";
|
||||||
|
|
||||||
enum ResponseType {
|
enum ResponseType {
|
||||||
|
|
@ -132,6 +132,8 @@ class BaseApiService {
|
||||||
|
|
||||||
switch (response.status) {
|
switch (response.status) {
|
||||||
case 401:
|
case 401:
|
||||||
|
// Use centralized auth handler for 401 responses
|
||||||
|
handleUnauthorized();
|
||||||
throw new AuthenticationError(
|
throw new AuthenticationError(
|
||||||
"You are not authenticated. Please login again.",
|
"You are not authenticated. Please login again.",
|
||||||
response.status,
|
response.status,
|
||||||
|
|
@ -261,6 +263,6 @@ class BaseApiService {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const baseApiService = new BaseApiService(
|
export const baseApiService = new BaseApiService(
|
||||||
typeof window !== "undefined" ? localStorage.getItem("surfsense_bearer_token") || "" : "",
|
typeof window !== "undefined" ? getBearerToken() || "" : "",
|
||||||
process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL || ""
|
process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL || ""
|
||||||
);
|
);
|
||||||
|
|
|
||||||
173
surfsense_web/lib/auth-utils.ts
Normal file
173
surfsense_web/lib/auth-utils.ts
Normal file
|
|
@ -0,0 +1,173 @@
|
||||||
|
/**
|
||||||
|
* Authentication utilities for handling token expiration and redirects
|
||||||
|
*/
|
||||||
|
|
||||||
|
const REDIRECT_PATH_KEY = "surfsense_redirect_path";
|
||||||
|
const BEARER_TOKEN_KEY = "surfsense_bearer_token";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the current path and redirects to login page
|
||||||
|
* Call this when a 401 response is received
|
||||||
|
*/
|
||||||
|
export function handleUnauthorized(): void {
|
||||||
|
if (typeof window === "undefined") return;
|
||||||
|
|
||||||
|
// Save the current path (including search params and hash) for redirect after login
|
||||||
|
const currentPath = window.location.pathname + window.location.search + window.location.hash;
|
||||||
|
|
||||||
|
// Don't save auth-related paths
|
||||||
|
const excludedPaths = ["/auth", "/auth/callback", "/"];
|
||||||
|
if (!excludedPaths.includes(window.location.pathname)) {
|
||||||
|
localStorage.setItem(REDIRECT_PATH_KEY, currentPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the token
|
||||||
|
localStorage.removeItem(BEARER_TOKEN_KEY);
|
||||||
|
|
||||||
|
// Redirect to home page (which has login options)
|
||||||
|
window.location.href = "/login";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the stored redirect path and clears it from storage
|
||||||
|
* Call this after successful login to redirect the user back
|
||||||
|
*/
|
||||||
|
export function getAndClearRedirectPath(): string | null {
|
||||||
|
if (typeof window === "undefined") return null;
|
||||||
|
|
||||||
|
const redirectPath = localStorage.getItem(REDIRECT_PATH_KEY);
|
||||||
|
if (redirectPath) {
|
||||||
|
localStorage.removeItem(REDIRECT_PATH_KEY);
|
||||||
|
}
|
||||||
|
return redirectPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the bearer token from localStorage
|
||||||
|
*/
|
||||||
|
export function getBearerToken(): string | null {
|
||||||
|
if (typeof window === "undefined") return null;
|
||||||
|
return localStorage.getItem(BEARER_TOKEN_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the bearer token in localStorage
|
||||||
|
*/
|
||||||
|
export function setBearerToken(token: string): void {
|
||||||
|
if (typeof window === "undefined") return;
|
||||||
|
localStorage.setItem(BEARER_TOKEN_KEY, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the bearer token from localStorage
|
||||||
|
*/
|
||||||
|
export function clearBearerToken(): void {
|
||||||
|
if (typeof window === "undefined") return;
|
||||||
|
localStorage.removeItem(BEARER_TOKEN_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the user is authenticated (has a token)
|
||||||
|
*/
|
||||||
|
export function isAuthenticated(): boolean {
|
||||||
|
return !!getBearerToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the current path and redirects to login page
|
||||||
|
* Use this for client-side auth checks (e.g., in useEffect)
|
||||||
|
* Unlike handleUnauthorized, this doesn't clear the token (user might not have one)
|
||||||
|
*/
|
||||||
|
export function redirectToLogin(): void {
|
||||||
|
if (typeof window === "undefined") return;
|
||||||
|
|
||||||
|
// Save the current path (including search params and hash) for redirect after login
|
||||||
|
const currentPath = window.location.pathname + window.location.search + window.location.hash;
|
||||||
|
|
||||||
|
// Don't save auth-related paths or home page
|
||||||
|
const excludedPaths = ["/auth", "/auth/callback", "/", "/login", "/register"];
|
||||||
|
if (!excludedPaths.includes(window.location.pathname)) {
|
||||||
|
localStorage.setItem(REDIRECT_PATH_KEY, currentPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect to login page
|
||||||
|
window.location.href = "/login";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates headers with authorization bearer token
|
||||||
|
*/
|
||||||
|
export function getAuthHeaders(additionalHeaders?: Record<string, string>): Record<string, string> {
|
||||||
|
const token = getBearerToken();
|
||||||
|
return {
|
||||||
|
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||||
|
...additionalHeaders,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authenticated fetch wrapper that handles 401 responses uniformly
|
||||||
|
* Automatically redirects to login on 401 and saves the current path
|
||||||
|
*/
|
||||||
|
export async function authenticatedFetch(
|
||||||
|
url: string,
|
||||||
|
options?: RequestInit & { skipAuthRedirect?: boolean }
|
||||||
|
): Promise<Response> {
|
||||||
|
const { skipAuthRedirect = false, ...fetchOptions } = options || {};
|
||||||
|
|
||||||
|
const headers = getAuthHeaders(fetchOptions.headers as Record<string, string>);
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
...fetchOptions,
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle 401 Unauthorized
|
||||||
|
if (response.status === 401 && !skipAuthRedirect) {
|
||||||
|
handleUnauthorized();
|
||||||
|
throw new Error("Unauthorized: Redirecting to login page");
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type for the result of a fetch operation with built-in error handling
|
||||||
|
*/
|
||||||
|
export type FetchResult<T> =
|
||||||
|
| { success: true; data: T; response: Response }
|
||||||
|
| { success: false; error: string; status?: number };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authenticated fetch with JSON response handling
|
||||||
|
* Returns a result object instead of throwing on non-401 errors
|
||||||
|
*/
|
||||||
|
export async function authenticatedFetchJson<T = unknown>(
|
||||||
|
url: string,
|
||||||
|
options?: RequestInit & { skipAuthRedirect?: boolean }
|
||||||
|
): Promise<FetchResult<T>> {
|
||||||
|
try {
|
||||||
|
const response = await authenticatedFetch(url, options);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: errorData.detail || `Request failed: ${response.status}`,
|
||||||
|
status: response.status,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return { success: true, data, response };
|
||||||
|
} catch (err: any) {
|
||||||
|
// Re-throw if it's the unauthorized redirect
|
||||||
|
if (err.message?.includes("Unauthorized")) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: err.message || "Request failed",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue