mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-10-05 17:53:54 +02:00
initial health check
This commit is contained in:
@@ -1,7 +1,8 @@
|
|||||||
FROM python:3.11-slim-bullseye
|
FROM python:3.11-slim-bullseye
|
||||||
|
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install -y git cmake pkg-config libprotobuf-c-dev protobuf-compiler libprotobuf-dev libgoogle-perftools-dev libpq-dev build-essential \
|
&& apt-get install -y git cmake pkg-config libprotobuf-c-dev protobuf-compiler \
|
||||||
|
libprotobuf-dev libgoogle-perftools-dev libpq-dev build-essential curl \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
COPY ./requirements/default.txt /tmp/requirements.txt
|
COPY ./requirements/default.txt /tmp/requirements.txt
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
FROM python:3.11-slim-bullseye
|
FROM python:3.11-slim-bullseye
|
||||||
|
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install -y git cmake pkg-config libprotobuf-c-dev protobuf-compiler libprotobuf-dev libgoogle-perftools-dev libpq-dev build-essential cron \
|
&& apt-get install -y git cmake pkg-config libprotobuf-c-dev protobuf-compiler \
|
||||||
|
libprotobuf-dev libgoogle-perftools-dev libpq-dev build-essential cron curl \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
COPY ./requirements/default.txt /tmp/requirements.txt
|
COPY ./requirements/default.txt /tmp/requirements.txt
|
||||||
|
@@ -13,6 +13,7 @@ from danswer.configs.app_configs import WEB_DOMAIN
|
|||||||
from danswer.datastores.qdrant.indexing import list_collections
|
from danswer.datastores.qdrant.indexing import list_collections
|
||||||
from danswer.server.admin import router as admin_router
|
from danswer.server.admin import router as admin_router
|
||||||
from danswer.server.event_loading import router as event_processing_router
|
from danswer.server.event_loading import router as event_processing_router
|
||||||
|
from danswer.server.health import router as health_router
|
||||||
from danswer.server.search_backend import router as backend_router
|
from danswer.server.search_backend import router as backend_router
|
||||||
from danswer.utils.logging import setup_logger
|
from danswer.utils.logging import setup_logger
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
@@ -39,6 +40,7 @@ def get_application() -> FastAPI:
|
|||||||
application.include_router(backend_router)
|
application.include_router(backend_router)
|
||||||
application.include_router(event_processing_router)
|
application.include_router(event_processing_router)
|
||||||
application.include_router(admin_router)
|
application.include_router(admin_router)
|
||||||
|
application.include_router(health_router)
|
||||||
|
|
||||||
application.include_router(
|
application.include_router(
|
||||||
fastapi_users.get_auth_router(auth_backend),
|
fastapi_users.get_auth_router(auth_backend),
|
||||||
|
@@ -21,7 +21,9 @@ from danswer.dynamic_configs.interface import ConfigNotFoundError
|
|||||||
from danswer.server.models import AuthStatus
|
from danswer.server.models import AuthStatus
|
||||||
from danswer.server.models import AuthUrl
|
from danswer.server.models import AuthUrl
|
||||||
from danswer.server.models import GDriveCallback
|
from danswer.server.models import GDriveCallback
|
||||||
|
from danswer.server.models import IndexAttemptRequest
|
||||||
from danswer.server.models import IndexAttemptSnapshot
|
from danswer.server.models import IndexAttemptSnapshot
|
||||||
|
from danswer.server.models import ListIndexAttemptsResponse
|
||||||
from danswer.utils.logging import setup_logger
|
from danswer.utils.logging import setup_logger
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
from fastapi import Depends
|
from fastapi import Depends
|
||||||
@@ -69,11 +71,6 @@ def modify_slack_config(
|
|||||||
update_slack_config(slack_config)
|
update_slack_config(slack_config)
|
||||||
|
|
||||||
|
|
||||||
class IndexAttemptRequest(BaseModel):
|
|
||||||
input_type: InputType = InputType.PULL
|
|
||||||
connector_specific_config: dict[str, Any]
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/connectors/{source}/index-attempt", status_code=201)
|
@router.post("/connectors/{source}/index-attempt", status_code=201)
|
||||||
def index(
|
def index(
|
||||||
source: DocumentSource,
|
source: DocumentSource,
|
||||||
@@ -100,10 +97,6 @@ def index(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ListIndexAttemptsResponse(BaseModel):
|
|
||||||
index_attempts: list[IndexAttemptSnapshot]
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/connectors/{source}/index-attempt")
|
@router.get("/connectors/{source}/index-attempt")
|
||||||
def list_index_attempts(
|
def list_index_attempts(
|
||||||
source: DocumentSource,
|
source: DocumentSource,
|
||||||
|
10
backend/danswer/server/health.py
Normal file
10
backend/danswer/server/health.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from danswer.server.models import HealthCheckResponse
|
||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/health")
|
||||||
|
def healthcheck() -> HealthCheckResponse:
|
||||||
|
return {"status": "ok"}
|
@@ -1,12 +1,18 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
from danswer.configs.constants import DocumentSource
|
from danswer.configs.constants import DocumentSource
|
||||||
|
from danswer.connectors.models import InputType
|
||||||
from danswer.datastores.interfaces import DatastoreFilter
|
from danswer.datastores.interfaces import DatastoreFilter
|
||||||
from danswer.db.models import IndexingStatus
|
from danswer.db.models import IndexingStatus
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class HealthCheckResponse(BaseModel):
|
||||||
|
status: Literal["ok"]
|
||||||
|
|
||||||
|
|
||||||
class AuthStatus(BaseModel):
|
class AuthStatus(BaseModel):
|
||||||
authenticated: bool
|
authenticated: bool
|
||||||
|
|
||||||
@@ -51,6 +57,11 @@ class UserByEmail(BaseModel):
|
|||||||
user_email: str
|
user_email: str
|
||||||
|
|
||||||
|
|
||||||
|
class IndexAttemptRequest(BaseModel):
|
||||||
|
input_type: InputType = InputType.PULL
|
||||||
|
connector_specific_config: dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
class IndexAttemptSnapshot(BaseModel):
|
class IndexAttemptSnapshot(BaseModel):
|
||||||
connector_specific_config: dict[str, Any]
|
connector_specific_config: dict[str, Any]
|
||||||
status: IndexingStatus
|
status: IndexingStatus
|
||||||
@@ -60,5 +71,5 @@ class IndexAttemptSnapshot(BaseModel):
|
|||||||
docs_indexed: int
|
docs_indexed: int
|
||||||
|
|
||||||
|
|
||||||
class ListWebsiteIndexAttemptsResponse(BaseModel):
|
class ListIndexAttemptsResponse(BaseModel):
|
||||||
index_attempts: list[IndexAttemptSnapshot]
|
index_attempts: list[IndexAttemptSnapshot]
|
||||||
|
@@ -35,7 +35,7 @@ services:
|
|||||||
web_server:
|
web_server:
|
||||||
build:
|
build:
|
||||||
context: ../web
|
context: ../web
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile.dev
|
||||||
depends_on:
|
depends_on:
|
||||||
- api_server
|
- api_server
|
||||||
restart: always
|
restart: always
|
||||||
@@ -43,7 +43,6 @@ services:
|
|||||||
- .env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
- INTERNAL_URL=http://api_server:8080
|
- INTERNAL_URL=http://api_server:8080
|
||||||
- NODE_ENV=development
|
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
relational_db:
|
relational_db:
|
||||||
@@ -59,6 +58,8 @@ services:
|
|||||||
vector_db:
|
vector_db:
|
||||||
image: qdrant/qdrant:v1.1.3
|
image: qdrant/qdrant:v1.1.3
|
||||||
restart: always
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "6333:6333"
|
||||||
volumes:
|
volumes:
|
||||||
- qdrant_volume:/qdrant/storage
|
- qdrant_volume:/qdrant/storage
|
||||||
volumes:
|
volumes:
|
||||||
|
@@ -33,7 +33,7 @@ services:
|
|||||||
web_server:
|
web_server:
|
||||||
build:
|
build:
|
||||||
context: ../web
|
context: ../web
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile.prod
|
||||||
depends_on:
|
depends_on:
|
||||||
- api_server
|
- api_server
|
||||||
restart: always
|
restart: always
|
||||||
@@ -41,7 +41,6 @@ services:
|
|||||||
- .env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
- INTERNAL_URL=http://api_server:8080
|
- INTERNAL_URL=http://api_server:8080
|
||||||
- NODE_ENV=production
|
|
||||||
relational_db:
|
relational_db:
|
||||||
image: postgres:15.2-alpine
|
image: postgres:15.2-alpine
|
||||||
restart: always
|
restart: always
|
||||||
|
32
web/Dockerfile.dev
Normal file
32
web/Dockerfile.dev
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
FROM node:18-alpine
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install dependencies based on the preferred package manager
|
||||||
|
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
|
||||||
|
RUN \
|
||||||
|
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
|
||||||
|
elif [ -f package-lock.json ]; then npm ci; \
|
||||||
|
elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i; \
|
||||||
|
# Allow install without lockfile, so example works even without Node.js installed locally
|
||||||
|
else echo "Warning: Lockfile not found. It is recommended to commit lockfiles to version control." && yarn install; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
COPY src ./src
|
||||||
|
COPY public ./public
|
||||||
|
COPY next.config.js .
|
||||||
|
COPY tsconfig.json .
|
||||||
|
|
||||||
|
# Next.js collects completely anonymous telemetry data about general usage. Learn more here: https://nextjs.org/telemetry
|
||||||
|
# Uncomment the following line to disable telemetry at run time
|
||||||
|
ENV NEXT_TELEMETRY_DISABLED 1
|
||||||
|
|
||||||
|
# Note: Don't expose ports here, Compose will handle that for us
|
||||||
|
|
||||||
|
# Start Next.js in development mode based on the preferred package manager
|
||||||
|
CMD \
|
||||||
|
if [ -f yarn.lock ]; then yarn dev; \
|
||||||
|
elif [ -f package-lock.json ]; then npm run dev; \
|
||||||
|
elif [ -f pnpm-lock.yaml ]; then pnpm dev; \
|
||||||
|
else yarn dev; \
|
||||||
|
fi
|
@@ -4,10 +4,14 @@ import * as Yup from "yup";
|
|||||||
import { IndexForm } from "@/components/admin/connectors/Form";
|
import { IndexForm } from "@/components/admin/connectors/Form";
|
||||||
import { GithubIcon } from "@/components/icons/icons";
|
import { GithubIcon } from "@/components/icons/icons";
|
||||||
import { TextFormField } from "@/components/admin/connectors/Field";
|
import { TextFormField } from "@/components/admin/connectors/Field";
|
||||||
|
import { HealthCheckBanner } from "@/components/health/healthcheck";
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto">
|
<div className="mx-auto">
|
||||||
|
<div className="mb-4">
|
||||||
|
<HealthCheckBanner />
|
||||||
|
</div>
|
||||||
<div className="border-solid border-gray-600 border-b mb-4 pb-2 flex">
|
<div className="border-solid border-gray-600 border-b mb-4 pb-2 flex">
|
||||||
<GithubIcon size="32" />
|
<GithubIcon size="32" />
|
||||||
<h1 className="text-3xl font-bold pl-2">Github PRs</h1>
|
<h1 className="text-3xl font-bold pl-2">Github PRs</h1>
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { getDomain } from "@/lib/redirectSS";
|
import { getDomain } from "@/lib/redirectSS";
|
||||||
import { buildUrl } from "@/lib/userSS";
|
import { buildUrl } from "@/lib/utilsSS";
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
|
||||||
export const GET = async (request: NextRequest) => {
|
export const GET = async (request: NextRequest) => {
|
||||||
|
@@ -16,6 +16,7 @@ import { LoadingAnimation } from "@/components/Loading";
|
|||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { Popup } from "@/components/admin/connectors/Popup";
|
import { Popup } from "@/components/admin/connectors/Popup";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { HealthCheckBanner } from "@/components/health/healthcheck";
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -43,9 +44,14 @@ export default function Page() {
|
|||||||
} | null>(null);
|
} | null>(null);
|
||||||
|
|
||||||
const header = (
|
const header = (
|
||||||
<div className="border-solid border-gray-600 border-b mb-4 pb-2 flex">
|
<div>
|
||||||
<GoogleDriveIcon size="32" />
|
<div className="mb-4">
|
||||||
<h1 className="text-3xl font-bold pl-2">Google Drive</h1>
|
<HealthCheckBanner />
|
||||||
|
</div>
|
||||||
|
<div className="border-solid border-gray-600 border-b mb-4 pb-2 flex">
|
||||||
|
<GoogleDriveIcon size="32" />
|
||||||
|
<h1 className="text-3xl font-bold pl-2">Google Drive</h1>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@@ -7,6 +7,7 @@ import { SlackConfig } from "../../../../components/admin/connectors/types";
|
|||||||
import { LoadingAnimation } from "@/components/Loading";
|
import { LoadingAnimation } from "@/components/Loading";
|
||||||
import { InitialSetupForm } from "./InitialSetupForm";
|
import { InitialSetupForm } from "./InitialSetupForm";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
import { HealthCheckBanner } from "@/components/health/healthcheck";
|
||||||
|
|
||||||
const MainSection = () => {
|
const MainSection = () => {
|
||||||
// TODO: add back in once this is ready
|
// TODO: add back in once this is ready
|
||||||
@@ -29,9 +30,7 @@ const MainSection = () => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (error || !data) {
|
} else if (error || !data) {
|
||||||
return (
|
return <div>{`Error loading Slack config - ${error}`}</div>;
|
||||||
<div className="mt-16">{`Error loading Slack config - ${error}`}</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -62,6 +61,9 @@ const MainSection = () => {
|
|||||||
export default function Page() {
|
export default function Page() {
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto">
|
<div className="mx-auto">
|
||||||
|
<div className="mb-4">
|
||||||
|
<HealthCheckBanner />
|
||||||
|
</div>
|
||||||
<div className="border-solid border-gray-600 border-b mb-4 pb-2 flex">
|
<div className="border-solid border-gray-600 border-b mb-4 pb-2 flex">
|
||||||
<SlackIcon size="32" />
|
<SlackIcon size="32" />
|
||||||
<h1 className="text-3xl font-bold pl-2">Slack</h1>
|
<h1 className="text-3xl font-bold pl-2">Slack</h1>
|
||||||
|
@@ -15,6 +15,7 @@ import {
|
|||||||
import { IndexForm } from "@/components/admin/connectors/Form";
|
import { IndexForm } from "@/components/admin/connectors/Form";
|
||||||
import { TextFormField } from "@/components/admin/connectors/Field";
|
import { TextFormField } from "@/components/admin/connectors/Field";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
import { HealthCheckBanner } from "@/components/health/healthcheck";
|
||||||
|
|
||||||
const COLUMNS = [
|
const COLUMNS = [
|
||||||
{ header: "Base URL", key: "url" },
|
{ header: "Base URL", key: "url" },
|
||||||
@@ -54,6 +55,9 @@ export default function Web() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto">
|
<div className="mx-auto">
|
||||||
|
<div className="mb-4">
|
||||||
|
<HealthCheckBanner />
|
||||||
|
</div>
|
||||||
<div className="border-solid border-gray-600 border-b pb-2 mb-4 flex">
|
<div className="border-solid border-gray-600 border-b pb-2 mb-4 flex">
|
||||||
<GlobeIcon size="32" />
|
<GlobeIcon size="32" />
|
||||||
<h1 className="text-3xl font-bold pl-2">Web</h1>
|
<h1 className="text-3xl font-bold pl-2">Web</h1>
|
||||||
|
@@ -16,6 +16,7 @@ import { CheckCircle, XCircle } from "@phosphor-icons/react";
|
|||||||
import { submitIndexRequest } from "@/components/admin/connectors/Form";
|
import { submitIndexRequest } from "@/components/admin/connectors/Form";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Popup } from "@/components/admin/connectors/Popup";
|
import { Popup } from "@/components/admin/connectors/Popup";
|
||||||
|
import { HealthCheckBanner } from "@/components/health/healthcheck";
|
||||||
|
|
||||||
const getModifiedSource = (indexAttempt: IndexAttempt) => {
|
const getModifiedSource = (indexAttempt: IndexAttempt) => {
|
||||||
return indexAttempt.source === "web"
|
return indexAttempt.source === "web"
|
||||||
@@ -63,6 +64,9 @@ export default function Status() {
|
|||||||
return (
|
return (
|
||||||
<div className="mx-auto">
|
<div className="mx-auto">
|
||||||
{popup && <Popup message={popup.message} type={popup.type} />}
|
{popup && <Popup message={popup.message} type={popup.type} />}
|
||||||
|
<div className="mb-4">
|
||||||
|
<HealthCheckBanner />
|
||||||
|
</div>
|
||||||
<div className="border-solid border-gray-600 border-b pb-2 mb-4 flex">
|
<div className="border-solid border-gray-600 border-b pb-2 mb-4 flex">
|
||||||
<NotebookIcon size="32" />
|
<NotebookIcon size="32" />
|
||||||
<h1 className="text-3xl font-bold pl-2">Indexing Status</h1>
|
<h1 className="text-3xl font-bold pl-2">Indexing Status</h1>
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { getDomain } from "@/lib/redirectSS";
|
import { getDomain } from "@/lib/redirectSS";
|
||||||
import { buildUrl } from "@/lib/userSS";
|
import { buildUrl } from "@/lib/utilsSS";
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
|
||||||
export const GET = async (request: NextRequest) => {
|
export const GET = async (request: NextRequest) => {
|
||||||
|
@@ -3,6 +3,7 @@ import { Header } from "@/components/Header";
|
|||||||
import { getCurrentUserSS } from "@/lib/userSS";
|
import { getCurrentUserSS } from "@/lib/userSS";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { DISABLE_AUTH } from "@/lib/constants";
|
import { DISABLE_AUTH } from "@/lib/constants";
|
||||||
|
import { HealthCheckBanner } from "@/components/health/healthcheck";
|
||||||
|
|
||||||
export default async function Home() {
|
export default async function Home() {
|
||||||
let user = null;
|
let user = null;
|
||||||
@@ -15,6 +16,9 @@ export default async function Home() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Header user={user} />
|
<Header user={user} />
|
||||||
|
<div className="m-3">
|
||||||
|
<HealthCheckBanner />
|
||||||
|
</div>
|
||||||
<div className="px-24 pt-10 flex flex-col items-center min-h-screen bg-gray-900 text-gray-100">
|
<div className="px-24 pt-10 flex flex-col items-center min-h-screen bg-gray-900 text-gray-100">
|
||||||
<div className="max-w-[800px] w-full">
|
<div className="max-w-[800px] w-full">
|
||||||
<SearchSection />
|
<SearchSection />
|
||||||
|
24
web/src/components/health/healthcheck.tsx
Normal file
24
web/src/components/health/healthcheck.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { fetcher } from "@/lib/fetcher";
|
||||||
|
import useSWR from "swr";
|
||||||
|
|
||||||
|
export const HealthCheckBanner = () => {
|
||||||
|
const { error } = useSWR("/api/health", fetcher);
|
||||||
|
if (!error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="text-xs mx-auto bg-gradient-to-r from-red-900 to-red-700 p-2 rounded-sm border-hidden text-gray-300">
|
||||||
|
<p className="font-bold pb-1">The backend is currently unavailable.</p>
|
||||||
|
|
||||||
|
<p className="px-1">
|
||||||
|
If this is your initial setup or you just updated your Danswer
|
||||||
|
deployment, this is likely because the backend is still starting up.
|
||||||
|
Give it a minute or two, and then refresh the page. If that does not
|
||||||
|
work, make sure the backend is setup and/or contact an administrator.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@@ -1,13 +1,6 @@
|
|||||||
import { cookies } from "next/headers";
|
import { cookies } from "next/headers";
|
||||||
import { User } from "./types";
|
import { User } from "./types";
|
||||||
import { INTERNAL_URL } from "./constants";
|
import { buildUrl } from "./utilsSS";
|
||||||
|
|
||||||
export const buildUrl = (path: string) => {
|
|
||||||
if (path.startsWith("/")) {
|
|
||||||
return `${INTERNAL_URL}${path}`;
|
|
||||||
}
|
|
||||||
return `${INTERNAL_URL}/${path}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getGoogleOAuthUrlSS = async (): Promise<string> => {
|
export const getGoogleOAuthUrlSS = async (): Promise<string> => {
|
||||||
const res = await fetch(buildUrl("/auth/google/authorize"));
|
const res = await fetch(buildUrl("/auth/google/authorize"));
|
||||||
|
8
web/src/lib/utilsSS.ts
Normal file
8
web/src/lib/utilsSS.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { INTERNAL_URL } from "./constants";
|
||||||
|
|
||||||
|
export const buildUrl = (path: string) => {
|
||||||
|
if (path.startsWith("/")) {
|
||||||
|
return `${INTERNAL_URL}${path}`;
|
||||||
|
}
|
||||||
|
return `${INTERNAL_URL}/${path}`;
|
||||||
|
};
|
Reference in New Issue
Block a user