Merge pull request #460 from MODSetter/dev

feat: add ProxyHeadersMiddleware and refactored  API endpoint paths to prevent redirects
This commit is contained in:
Rohan Verma 2025-10-31 01:38:51 -07:00 committed by GitHub
commit 9ce00caf13
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
42 changed files with 196 additions and 148 deletions

View file

@ -3,6 +3,7 @@ from contextlib import asynccontextmanager
from fastapi import Depends, FastAPI, HTTPException, status from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
from app.config import config from app.config import config
from app.db import User, create_db_and_tables, get_async_session from app.db import User, create_db_and_tables, get_async_session
@ -28,6 +29,10 @@ def registration_allowed():
app = FastAPI(lifespan=lifespan) app = FastAPI(lifespan=lifespan)
# Add ProxyHeaders middleware FIRST to trust proxy headers (e.g., from Cloudflare)
# This ensures FastAPI uses HTTPS in redirects when behind a proxy
app.add_middleware(ProxyHeadersMiddleware, trusted_hosts="*")
# Add CORS middleware # Add CORS middleware
app.add_middleware( app.add_middleware(
CORSMiddleware, CORSMiddleware,

View file

@ -69,7 +69,7 @@ def generate_pkce_pair() -> tuple[str, str]:
return code_verifier, code_challenge return code_verifier, code_challenge
@router.get("/auth/airtable/connector/add/") @router.get("/auth/airtable/connector/add")
async def connect_airtable(space_id: int, user: User = Depends(current_active_user)): async def connect_airtable(space_id: int, user: User = Depends(current_active_user)):
""" """
Initiate Airtable OAuth flow. Initiate Airtable OAuth flow.
@ -131,7 +131,7 @@ async def connect_airtable(space_id: int, user: User = Depends(current_active_us
) from e ) from e
@router.get("/auth/airtable/connector/callback/") @router.get("/auth/airtable/connector/callback")
async def airtable_callback( async def airtable_callback(
request: Request, request: Request,
code: str, code: str,

View file

@ -130,7 +130,7 @@ async def handle_chat_data(
return response return response
@router.post("/chats/", response_model=ChatRead) @router.post("/chats", response_model=ChatRead)
async def create_chat( async def create_chat(
chat: ChatCreate, chat: ChatCreate,
session: AsyncSession = Depends(get_async_session), session: AsyncSession = Depends(get_async_session),
@ -164,7 +164,7 @@ async def create_chat(
) from None ) from None
@router.get("/chats/", response_model=list[ChatReadWithoutMessages]) @router.get("/chats", response_model=list[ChatReadWithoutMessages])
async def read_chats( async def read_chats(
skip: int = 0, skip: int = 0,
limit: int = 100, limit: int = 100,

View file

@ -38,7 +38,7 @@ os.environ["UNSTRUCTURED_HAS_PATCHED_LOOP"] = "1"
router = APIRouter() router = APIRouter()
@router.post("/documents/") @router.post("/documents")
async def create_documents( async def create_documents(
request: DocumentsCreate, request: DocumentsCreate,
session: AsyncSession = Depends(get_async_session), session: AsyncSession = Depends(get_async_session),
@ -147,7 +147,7 @@ async def create_documents_file_upload(
) from e ) from e
@router.get("/documents/", response_model=PaginatedResponse[DocumentRead]) @router.get("/documents", response_model=PaginatedResponse[DocumentRead])
async def read_documents( async def read_documents(
skip: int | None = None, skip: int | None = None,
page: int | None = None, page: int | None = None,
@ -248,7 +248,7 @@ async def read_documents(
) from e ) from e
@router.get("/documents/search/", response_model=PaginatedResponse[DocumentRead]) @router.get("/documents/search", response_model=PaginatedResponse[DocumentRead])
async def search_documents( async def search_documents(
title: str, title: str,
skip: int | None = None, skip: int | None = None,
@ -353,6 +353,103 @@ async def search_documents(
) from e ) from e
@router.get("/documents/type-counts")
async def get_document_type_counts(
search_space_id: int | None = None,
session: AsyncSession = Depends(get_async_session),
user: User = Depends(current_active_user),
):
"""
Get counts of documents by type for the current user.
Args:
search_space_id: If provided, restrict counts to a specific search space.
session: Database session (injected).
user: Current authenticated user (injected).
Returns:
Dict mapping document types to their counts.
"""
try:
from sqlalchemy import func
query = (
select(Document.document_type, func.count(Document.id))
.join(SearchSpace)
.filter(SearchSpace.user_id == user.id)
.group_by(Document.document_type)
)
if search_space_id is not None:
query = query.filter(Document.search_space_id == search_space_id)
result = await session.execute(query)
type_counts = dict(result.all())
return type_counts
except Exception as e:
raise HTTPException(
status_code=500, detail=f"Failed to fetch document type counts: {e!s}"
) from e
@router.get("/documents/by-chunk/{chunk_id}", response_model=DocumentWithChunksRead)
async def get_document_by_chunk_id(
chunk_id: int,
session: AsyncSession = Depends(get_async_session),
user: User = Depends(current_active_user),
):
"""
Retrieves a document based on a chunk ID, including all its chunks ordered by creation time.
The document's embedding and chunk embeddings are excluded from the response.
"""
try:
# First, get the chunk and verify it exists
chunk_result = await session.execute(select(Chunk).filter(Chunk.id == chunk_id))
chunk = chunk_result.scalars().first()
if not chunk:
raise HTTPException(
status_code=404, detail=f"Chunk with id {chunk_id} not found"
)
# Get the associated document and verify ownership
document_result = await session.execute(
select(Document)
.options(selectinload(Document.chunks))
.join(SearchSpace)
.filter(Document.id == chunk.document_id, SearchSpace.user_id == user.id)
)
document = document_result.scalars().first()
if not document:
raise HTTPException(
status_code=404,
detail="Document not found or you don't have access to it",
)
# Sort chunks by creation time
sorted_chunks = sorted(document.chunks, key=lambda x: x.created_at)
# Return the document with its chunks
return DocumentWithChunksRead(
id=document.id,
title=document.title,
document_type=document.document_type,
document_metadata=document.document_metadata,
content=document.content,
created_at=document.created_at,
search_space_id=document.search_space_id,
chunks=sorted_chunks,
)
except HTTPException:
raise
except Exception as e:
raise HTTPException(
status_code=500, detail=f"Failed to retrieve document: {e!s}"
) from e
@router.get("/documents/{document_id}", response_model=DocumentRead) @router.get("/documents/{document_id}", response_model=DocumentRead)
async def read_document( async def read_document(
document_id: int, document_id: int,
@ -464,100 +561,3 @@ async def delete_document(
raise HTTPException( raise HTTPException(
status_code=500, detail=f"Failed to delete document: {e!s}" status_code=500, detail=f"Failed to delete document: {e!s}"
) from e ) from e
@router.get("/documents/type-counts/")
async def get_document_type_counts(
search_space_id: int | None = None,
session: AsyncSession = Depends(get_async_session),
user: User = Depends(current_active_user),
):
"""
Get counts of documents by type for the current user.
Args:
search_space_id: If provided, restrict counts to a specific search space.
session: Database session (injected).
user: Current authenticated user (injected).
Returns:
Dict mapping document types to their counts.
"""
try:
from sqlalchemy import func
query = (
select(Document.document_type, func.count(Document.id))
.join(SearchSpace)
.filter(SearchSpace.user_id == user.id)
.group_by(Document.document_type)
)
if search_space_id is not None:
query = query.filter(Document.search_space_id == search_space_id)
result = await session.execute(query)
type_counts = dict(result.all())
return type_counts
except Exception as e:
raise HTTPException(
status_code=500, detail=f"Failed to fetch document type counts: {e!s}"
) from e
@router.get("/documents/by-chunk/{chunk_id}", response_model=DocumentWithChunksRead)
async def get_document_by_chunk_id(
chunk_id: int,
session: AsyncSession = Depends(get_async_session),
user: User = Depends(current_active_user),
):
"""
Retrieves a document based on a chunk ID, including all its chunks ordered by creation time.
The document's embedding and chunk embeddings are excluded from the response.
"""
try:
# First, get the chunk and verify it exists
chunk_result = await session.execute(select(Chunk).filter(Chunk.id == chunk_id))
chunk = chunk_result.scalars().first()
if not chunk:
raise HTTPException(
status_code=404, detail=f"Chunk with id {chunk_id} not found"
)
# Get the associated document and verify ownership
document_result = await session.execute(
select(Document)
.options(selectinload(Document.chunks))
.join(SearchSpace)
.filter(Document.id == chunk.document_id, SearchSpace.user_id == user.id)
)
document = document_result.scalars().first()
if not document:
raise HTTPException(
status_code=404,
detail="Document not found or you don't have access to it",
)
# Sort chunks by creation time
sorted_chunks = sorted(document.chunks, key=lambda x: x.created_at)
# Return the document with its chunks
return DocumentWithChunksRead(
id=document.id,
title=document.title,
document_type=document.document_type,
document_metadata=document.document_metadata,
content=document.content,
created_at=document.created_at,
search_space_id=document.search_space_id,
chunks=sorted_chunks,
)
except HTTPException:
raise
except Exception as e:
raise HTTPException(
status_code=500, detail=f"Failed to retrieve document: {e!s}"
) from e

View file

@ -53,7 +53,7 @@ def get_google_flow():
) from e ) from e
@router.get("/auth/google/calendar/connector/add/") @router.get("/auth/google/calendar/connector/add")
async def connect_calendar(space_id: int, user: User = Depends(current_active_user)): async def connect_calendar(space_id: int, user: User = Depends(current_active_user)):
try: try:
if not space_id: if not space_id:
@ -83,7 +83,7 @@ async def connect_calendar(space_id: int, user: User = Depends(current_active_us
) from e ) from e
@router.get("/auth/google/calendar/connector/callback/") @router.get("/auth/google/calendar/connector/callback")
async def calendar_callback( async def calendar_callback(
request: Request, request: Request,
code: str, code: str,

View file

@ -52,7 +52,7 @@ def get_google_flow():
return flow return flow
@router.get("/auth/google/gmail/connector/add/") @router.get("/auth/google/gmail/connector/add")
async def connect_gmail(space_id: int, user: User = Depends(current_active_user)): async def connect_gmail(space_id: int, user: User = Depends(current_active_user)):
try: try:
if not space_id: if not space_id:
@ -82,7 +82,7 @@ async def connect_gmail(space_id: int, user: User = Depends(current_active_user)
) from e ) from e
@router.get("/auth/google/gmail/connector/callback/") @router.get("/auth/google/gmail/connector/callback")
async def gmail_callback( async def gmail_callback(
request: Request, request: Request,
code: str, code: str,

View file

@ -87,7 +87,7 @@ class LLMPreferencesRead(BaseModel):
strategic_llm: LLMConfigRead | None = None strategic_llm: LLMConfigRead | None = None
@router.post("/llm-configs/", response_model=LLMConfigRead) @router.post("/llm-configs", response_model=LLMConfigRead)
async def create_llm_config( async def create_llm_config(
llm_config: LLMConfigCreate, llm_config: LLMConfigCreate,
session: AsyncSession = Depends(get_async_session), session: AsyncSession = Depends(get_async_session),
@ -112,7 +112,7 @@ async def create_llm_config(
) from e ) from e
@router.get("/llm-configs/", response_model=list[LLMConfigRead]) @router.get("/llm-configs", response_model=list[LLMConfigRead])
async def read_llm_configs( async def read_llm_configs(
search_space_id: int, search_space_id: int,
skip: int = 0, skip: int = 0,

View file

@ -13,7 +13,7 @@ from app.utils.check_ownership import check_ownership
router = APIRouter() router = APIRouter()
@router.post("/logs/", response_model=LogRead) @router.post("/logs", response_model=LogRead)
async def create_log( async def create_log(
log: LogCreate, log: LogCreate,
session: AsyncSession = Depends(get_async_session), session: AsyncSession = Depends(get_async_session),
@ -38,7 +38,7 @@ async def create_log(
) from e ) from e
@router.get("/logs/", response_model=list[LogRead]) @router.get("/logs", response_model=list[LogRead])
async def read_logs( async def read_logs(
skip: int = 0, skip: int = 0,
limit: int = 100, limit: int = 100,

View file

@ -21,7 +21,7 @@ from app.utils.check_ownership import check_ownership
router = APIRouter() router = APIRouter()
@router.post("/podcasts/", response_model=PodcastRead) @router.post("/podcasts", response_model=PodcastRead)
async def create_podcast( async def create_podcast(
podcast: PodcastCreate, podcast: PodcastCreate,
session: AsyncSession = Depends(get_async_session), session: AsyncSession = Depends(get_async_session),
@ -54,7 +54,7 @@ async def create_podcast(
) from None ) from None
@router.get("/podcasts/", response_model=list[PodcastRead]) @router.get("/podcasts", response_model=list[PodcastRead])
async def read_podcasts( async def read_podcasts(
skip: int = 0, skip: int = 0,
limit: int = 100, limit: int = 100,
@ -171,7 +171,7 @@ async def generate_chat_podcast_with_new_session(
logging.error(f"Error generating podcast from chat: {e!s}") logging.error(f"Error generating podcast from chat: {e!s}")
@router.post("/podcasts/generate/") @router.post("/podcasts/generate")
async def generate_podcast( async def generate_podcast(
request: PodcastGenerateRequest, request: PodcastGenerateRequest,
session: AsyncSession = Depends(get_async_session), session: AsyncSession = Depends(get_async_session),

View file

@ -70,7 +70,7 @@ class GitHubPATRequest(BaseModel):
# --- New Endpoint to list GitHub Repositories --- # --- New Endpoint to list GitHub Repositories ---
@router.post("/github/repositories/", response_model=list[dict[str, Any]]) @router.post("/github/repositories", response_model=list[dict[str, Any]])
async def list_github_repositories( async def list_github_repositories(
pat_request: GitHubPATRequest, pat_request: GitHubPATRequest,
user: User = Depends(current_active_user), # Ensure the user is logged in user: User = Depends(current_active_user), # Ensure the user is logged in
@ -96,7 +96,7 @@ async def list_github_repositories(
) from e ) from e
@router.post("/search-source-connectors/", response_model=SearchSourceConnectorRead) @router.post("/search-source-connectors", response_model=SearchSourceConnectorRead)
async def create_search_source_connector( async def create_search_source_connector(
connector: SearchSourceConnectorCreate, connector: SearchSourceConnectorCreate,
search_space_id: int = Query( search_space_id: int = Query(
@ -189,9 +189,7 @@ async def create_search_source_connector(
) from e ) from e
@router.get( @router.get("/search-source-connectors", response_model=list[SearchSourceConnectorRead])
"/search-source-connectors/", response_model=list[SearchSourceConnectorRead]
)
async def read_search_source_connectors( async def read_search_source_connectors(
skip: int = 0, skip: int = 0,
limit: int = 100, limit: int = 100,

View file

@ -10,7 +10,7 @@ from app.utils.check_ownership import check_ownership
router = APIRouter() router = APIRouter()
@router.post("/searchspaces/", response_model=SearchSpaceRead) @router.post("/searchspaces", response_model=SearchSpaceRead)
async def create_search_space( async def create_search_space(
search_space: SearchSpaceCreate, search_space: SearchSpaceCreate,
session: AsyncSession = Depends(get_async_session), session: AsyncSession = Depends(get_async_session),
@ -31,7 +31,7 @@ async def create_search_space(
) from e ) from e
@router.get("/searchspaces/", response_model=list[SearchSpaceRead]) @router.get("/searchspaces", response_model=list[SearchSpaceRead])
async def read_search_spaces( async def read_search_spaces(
skip: int = 0, skip: int = 0,
limit: int = 200, limit: int = 200,

View file

@ -131,7 +131,7 @@ const handler: PlasmoMessaging.MessageHandler = async (req, res) => {
}; };
const response = await fetch( const response = await fetch(
`${process.env.PLASMO_PUBLIC_BACKEND_URL}/api/v1/documents/`, `${process.env.PLASMO_PUBLIC_BACKEND_URL}/api/v1/documents`,
requestOptions requestOptions
); );
const resp = await response.json(); const resp = await response.json();

View file

@ -123,7 +123,7 @@ const handler: PlasmoMessaging.MessageHandler = async (req, res) => {
}; };
const response = await fetch( const response = await fetch(
`${process.env.PLASMO_PUBLIC_BACKEND_URL}/api/v1/documents/`, `${process.env.PLASMO_PUBLIC_BACKEND_URL}/api/v1/documents`,
requestOptions requestOptions
); );
const resp = await response.json(); const resp = await response.json();

View file

@ -47,7 +47,7 @@ const HomePage = () => {
const token = await storage.get("token"); const token = await storage.get("token");
try { try {
const response = await fetch( const response = await fetch(
`${process.env.PLASMO_PUBLIC_BACKEND_URL}/api/v1/searchspaces/`, `${process.env.PLASMO_PUBLIC_BACKEND_URL}/api/v1/searchspaces`,
{ {
headers: { headers: {
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,

View file

@ -140,7 +140,7 @@ export default function ChatsPageClient({ searchSpaceId }: ChatsPageClientProps)
// Fetch all chats for this search space // Fetch all chats for this search space
const response = await fetch( const response = await fetch(
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/chats/?search_space_id=${searchSpaceId}`, `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/chats?search_space_id=${searchSpaceId}`,
{ {
headers: { headers: {
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
@ -285,7 +285,7 @@ export default function ChatsPageClient({ searchSpaceId }: ChatsPageClientProps)
}; };
const response = await fetch( const response = await fetch(
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/podcasts/generate/`, `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/podcasts/generate`,
{ {
method: "POST", method: "POST",
headers: { headers: {

View file

@ -102,6 +102,9 @@ export default function BaiduSearchApiPage() {
config, config,
is_indexable: false, is_indexable: false,
last_indexed_at: null, last_indexed_at: null,
periodic_indexing_enabled: false,
indexing_frequency_minutes: null,
next_scheduled_at: null,
}, },
parseInt(searchSpaceId) parseInt(searchSpaceId)
); );

View file

@ -67,6 +67,9 @@ export default function ClickUpConnectorPage() {
CLICKUP_API_TOKEN: values.api_token, CLICKUP_API_TOKEN: values.api_token,
}, },
last_indexed_at: null, last_indexed_at: null,
periodic_indexing_enabled: false,
indexing_frequency_minutes: null,
next_scheduled_at: null,
}; };
await createConnector(connectorData, parseInt(searchSpaceId)); await createConnector(connectorData, parseInt(searchSpaceId));

View file

@ -88,6 +88,9 @@ export default function ConfluenceConnectorPage() {
}, },
is_indexable: true, is_indexable: true,
last_indexed_at: null, last_indexed_at: null,
periodic_indexing_enabled: false,
indexing_frequency_minutes: null,
next_scheduled_at: null,
}, },
parseInt(searchSpaceId) parseInt(searchSpaceId)
); );

View file

@ -82,6 +82,9 @@ export default function DiscordConnectorPage() {
}, },
is_indexable: true, is_indexable: true,
last_indexed_at: null, last_indexed_at: null,
periodic_indexing_enabled: false,
indexing_frequency_minutes: null,
next_scheduled_at: null,
}, },
parseInt(searchSpaceId) parseInt(searchSpaceId)
); );

View file

@ -177,7 +177,10 @@ export default function ElasticsearchConnectorPage() {
name: values.name, name: values.name,
connector_type: EnumConnectorName.ELASTICSEARCH_CONNECTOR, connector_type: EnumConnectorName.ELASTICSEARCH_CONNECTOR,
is_indexable: true, is_indexable: true,
search_space_id: searchSpaceIdNum, last_indexed_at: null,
periodic_indexing_enabled: false,
indexing_frequency_minutes: null,
next_scheduled_at: null,
config, config,
}; };
@ -464,7 +467,7 @@ export default function ElasticsearchConnectorPage() {
<div className="rounded-lg border bg-muted/50 p-3"> <div className="rounded-lg border bg-muted/50 p-3">
<h4 className="text-sm font-medium mb-2">Selected Indices:</h4> <h4 className="text-sm font-medium mb-2">Selected Indices:</h4>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{stringToArray(form.watch("indices")).map((index) => ( {stringToArray(form.watch("indices") ?? "").map((index) => (
<Badge key={index} variant="secondary" className="text-xs"> <Badge key={index} variant="secondary" className="text-xs">
{index} {index}
</Badge> </Badge>
@ -543,7 +546,7 @@ export default function ElasticsearchConnectorPage() {
<div className="rounded-lg border bg-muted/50 p-3"> <div className="rounded-lg border bg-muted/50 p-3">
<h4 className="text-sm font-medium mb-2">Search Fields:</h4> <h4 className="text-sm font-medium mb-2">Search Fields:</h4>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{stringToArray(form.watch("search_fields")).map((field) => ( {stringToArray(form.watch("search_fields") ?? "").map((field) => (
<Badge key={field} variant="outline" className="text-xs"> <Badge key={field} variant="outline" className="text-xs">
{field} {field}
</Badge> </Badge>

View file

@ -107,7 +107,7 @@ export default function GithubConnectorPage() {
} }
const response = await fetch( 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: {
@ -158,6 +158,9 @@ export default function GithubConnectorPage() {
}, },
is_indexable: true, is_indexable: true,
last_indexed_at: null, last_indexed_at: null,
periodic_indexing_enabled: false,
indexing_frequency_minutes: null,
next_scheduled_at: null,
}, },
parseInt(searchSpaceId) parseInt(searchSpaceId)
); );

View file

@ -101,6 +101,9 @@ export default function JiraConnectorPage() {
}, },
is_indexable: true, is_indexable: true,
last_indexed_at: null, last_indexed_at: null,
periodic_indexing_enabled: false,
indexing_frequency_minutes: null,
next_scheduled_at: null,
}, },
parseInt(searchSpaceId) parseInt(searchSpaceId)
); );

View file

@ -86,6 +86,9 @@ export default function LinearConnectorPage() {
}, },
is_indexable: true, is_indexable: true,
last_indexed_at: null, last_indexed_at: null,
periodic_indexing_enabled: false,
indexing_frequency_minutes: null,
next_scheduled_at: null,
}, },
parseInt(searchSpaceId) parseInt(searchSpaceId)
); );

View file

@ -74,6 +74,9 @@ export default function LinkupApiPage() {
}, },
is_indexable: false, is_indexable: false,
last_indexed_at: null, last_indexed_at: null,
periodic_indexing_enabled: false,
indexing_frequency_minutes: null,
next_scheduled_at: null,
}, },
parseInt(searchSpaceId) parseInt(searchSpaceId)
); );

View file

@ -99,6 +99,9 @@ export default function LumaConnectorPage() {
}, },
is_indexable: true, is_indexable: true,
last_indexed_at: null, last_indexed_at: null,
periodic_indexing_enabled: false,
indexing_frequency_minutes: null,
next_scheduled_at: null,
}, },
parseInt(searchSpaceId) parseInt(searchSpaceId)
); );

View file

@ -81,6 +81,9 @@ export default function NotionConnectorPage() {
}, },
is_indexable: true, is_indexable: true,
last_indexed_at: null, last_indexed_at: null,
periodic_indexing_enabled: false,
indexing_frequency_minutes: null,
next_scheduled_at: null,
}, },
parseInt(searchSpaceId) parseInt(searchSpaceId)
); );

View file

@ -122,6 +122,9 @@ export default function SearxngConnectorPage() {
config, config,
is_indexable: false, is_indexable: false,
last_indexed_at: null, last_indexed_at: null,
periodic_indexing_enabled: false,
indexing_frequency_minutes: null,
next_scheduled_at: null,
}, },
parseInt(searchSpaceId) parseInt(searchSpaceId)
); );

View file

@ -74,6 +74,9 @@ export default function SerperApiPage() {
}, },
is_indexable: false, is_indexable: false,
last_indexed_at: null, last_indexed_at: null,
periodic_indexing_enabled: false,
indexing_frequency_minutes: null,
next_scheduled_at: null,
}, },
parseInt(searchSpaceId) parseInt(searchSpaceId)
); );

View file

@ -81,6 +81,9 @@ export default function SlackConnectorPage() {
}, },
is_indexable: true, is_indexable: true,
last_indexed_at: null, last_indexed_at: null,
periodic_indexing_enabled: false,
indexing_frequency_minutes: null,
next_scheduled_at: null,
}, },
parseInt(searchSpaceId) parseInt(searchSpaceId)
); );

View file

@ -74,6 +74,9 @@ export default function TavilyApiPage() {
}, },
is_indexable: false, is_indexable: false,
last_indexed_at: null, last_indexed_at: null,
periodic_indexing_enabled: false,
indexing_frequency_minutes: null,
next_scheduled_at: null,
}, },
parseInt(searchSpaceId) parseInt(searchSpaceId)
); );

View file

@ -64,7 +64,7 @@ export default function WebpageCrawler() {
// Make API call to backend // Make API call to backend
const response = await fetch( const response = await fetch(
`${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: {

View file

@ -73,7 +73,7 @@ export default function YouTubeVideoAdder() {
// Make API call to backend // Make API call to backend
const response = await fetch( const response = await fetch(
`${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: {

View file

@ -130,7 +130,7 @@ export default function PodcastsPageClient({ searchSpaceId }: PodcastsPageClient
// Fetch all podcasts for this search space // Fetch all podcasts for this search space
const response = await fetch( const response = await fetch(
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/podcasts/`, `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/podcasts`,
{ {
headers: { headers: {
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,

View file

@ -90,7 +90,7 @@ export function AppSidebarProvider({
if (typeof window === "undefined") return; if (typeof window === "undefined") return;
const chats: Chat[] = await apiClient.get<Chat[]>( const chats: Chat[] = await apiClient.get<Chat[]>(
`api/v1/chats/?limit=5&skip=0&search_space_id=${searchSpaceId}` `api/v1/chats?limit=5&skip=0&search_space_id=${searchSpaceId}`
); );
// Sort chats by created_at in descending order (newest first) // Sort chats by created_at in descending order (newest first)

View file

@ -90,7 +90,7 @@ export function useChatAPI({ token, search_space_id }: UseChatAPIProps) {
try { try {
const response = await fetch( const response = await fetch(
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/chats/`, `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/chats`,
{ {
method: "POST", method: "POST",
headers: { headers: {

View file

@ -177,7 +177,7 @@ export function useConnectorEditPage(connectorId: number, searchSpaceId: string)
const token = localStorage.getItem("surfsense_bearer_token"); const token = localStorage.getItem("surfsense_bearer_token");
if (!token) throw new Error("No auth token"); if (!token) throw new Error("No auth token");
const response = await fetch( 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: {

View file

@ -33,7 +33,7 @@ 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 fetch(
`${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: {
@ -55,7 +55,7 @@ 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 fetch(
`${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}`,
{ {
headers: { headers: {
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`, Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,

View file

@ -31,7 +31,7 @@ export const useDocumentTypes = (searchSpaceId?: number, lazy: boolean = false)
// 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(
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/documents/type-counts/` `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/documents/type-counts`
); );
if (spaceId !== undefined) { if (spaceId !== undefined) {
url.searchParams.append("search_space_id", spaceId.toString()); url.searchParams.append("search_space_id", spaceId.toString());

View file

@ -160,7 +160,7 @@ export function useDocuments(searchSpaceId: number, options?: UseDocumentsOption
} }
const response = await fetch( const response = await fetch(
`${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()}`,
{ {
headers: { headers: {
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`, Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
@ -229,7 +229,7 @@ export function useDocuments(searchSpaceId: number, options?: UseDocumentsOption
}); });
const response = await fetch( const response = await fetch(
`${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()}`,
{ {
headers: { headers: {
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`, Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,

View file

@ -61,7 +61,7 @@ export function useLLMConfigs(searchSpaceId: number | null) {
try { try {
setLoading(true); setLoading(true);
const response = await fetch( const response = await fetch(
`${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}`,
{ {
headers: { headers: {
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`, Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
@ -92,7 +92,7 @@ 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 fetch(
`${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: {

View file

@ -96,7 +96,7 @@ export function useLogs(searchSpaceId?: number, filters: LogFilters = {}) {
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 fetch(
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/logs/?${params}`, `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/logs?${params}`,
{ {
headers: { headers: {
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`, Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,
@ -147,7 +147,7 @@ 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 fetch(`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/logs`, {
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`, Authorization: `Bearer ${localStorage.getItem("surfsense_bearer_token")}`,

View file

@ -74,7 +74,7 @@ export const useSearchSourceConnectors = (lazy: boolean = false, searchSpaceId?:
// 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(
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-source-connectors/` `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-source-connectors`
); );
if (spaceId !== undefined) { if (spaceId !== undefined) {
url.searchParams.append("search_space_id", spaceId.toString()); url.searchParams.append("search_space_id", spaceId.toString());
@ -184,7 +184,7 @@ export const useSearchSourceConnectors = (lazy: boolean = false, searchSpaceId?:
// 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());