mirror of
https://github.com/sourcebot-dev/sourcebot.git
synced 2025-12-12 04:15:30 +00:00
Switch to using t3-env for env-var management (#230)
This commit is contained in:
parent
e8acfcca70
commit
483217bf56
49 changed files with 404 additions and 484 deletions
|
|
@ -1,11 +1,13 @@
|
|||
Dockerfile
|
||||
.dockerignore
|
||||
node_modules
|
||||
npm-debug.log
|
||||
README.md
|
||||
.next
|
||||
!.next/static
|
||||
!.next/standalone
|
||||
.git
|
||||
.sourcebot
|
||||
.env.local
|
||||
packages/web/.next
|
||||
!packages/web/.next/static
|
||||
!packages/web/.next/standalone
|
||||
**/node_modules
|
||||
**/.env.local
|
||||
**/.sentryclirc
|
||||
**/.env.sentry-build-plugin
|
||||
80
Dockerfile
80
Dockerfile
|
|
@ -1,5 +1,18 @@
|
|||
# ------ Global scope variables ------
|
||||
# Set of global build arguments.
|
||||
# @see: https://docs.docker.com/build/building/variables/#scoping
|
||||
|
||||
ARG SOURCEBOT_VERSION
|
||||
# PAPIK = Project API Key
|
||||
# Note that this key does not need to be kept secret, so it's not
|
||||
# necessary to use Docker build secrets here.
|
||||
# @see: https://posthog.com/tutorials/api-capture-events#authenticating-with-the-project-api-key
|
||||
ARG POSTHOG_PAPIK
|
||||
ARG SENTRY_ENVIRONMENT
|
||||
|
||||
FROM node:20-alpine3.19 AS node-alpine
|
||||
FROM golang:1.23.4-alpine3.19 AS go-alpine
|
||||
# ----------------------------------
|
||||
|
||||
# ------ Build Zoekt ------
|
||||
FROM go-alpine AS zoekt-builder
|
||||
|
|
@ -9,6 +22,7 @@ COPY vendor/zoekt/go.mod vendor/zoekt/go.sum ./
|
|||
RUN go mod download
|
||||
COPY vendor/zoekt ./
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -o /cmd/ ./cmd/...
|
||||
# -------------------------
|
||||
|
||||
# ------ Build shared libraries ------
|
||||
FROM node-alpine AS shared-libs-builder
|
||||
|
|
@ -23,9 +37,24 @@ RUN yarn workspace @sourcebot/db install --frozen-lockfile
|
|||
RUN yarn workspace @sourcebot/schemas install --frozen-lockfile
|
||||
RUN yarn workspace @sourcebot/crypto install --frozen-lockfile
|
||||
RUN yarn workspace @sourcebot/error install --frozen-lockfile
|
||||
# ------------------------------------
|
||||
|
||||
# ------ Build Web ------
|
||||
FROM node-alpine AS web-builder
|
||||
ENV DOCKER_BUILD=1
|
||||
# -----------
|
||||
# Global args
|
||||
ARG SOURCEBOT_VERSION
|
||||
ENV NEXT_PUBLIC_SOURCEBOT_VERSION=$SOURCEBOT_VERSION
|
||||
ARG POSTHOG_PAPIK
|
||||
ENV NEXT_PUBLIC_POSTHOG_PAPIK=$POSTHOG_PAPIK
|
||||
ARG SENTRY_ENVIRONMENT
|
||||
ENV NEXT_PUBLIC_SENTRY_ENVIRONMENT=$SENTRY_ENVIRONMENT
|
||||
# Local args
|
||||
ARG SENTRY_WEBAPP_DSN
|
||||
ENV NEXT_PUBLIC_SENTRY_WEBAPP_DSN=$SENTRY_WEBAPP_DSN
|
||||
# -----------
|
||||
|
||||
RUN apk add --no-cache libc6-compat
|
||||
WORKDIR /app
|
||||
|
||||
|
|
@ -43,26 +72,13 @@ RUN yarn config set network-timeout 1200000
|
|||
RUN yarn workspace @sourcebot/web install --frozen-lockfile
|
||||
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
# @see: https://phase.dev/blog/nextjs-public-runtime-variables/
|
||||
ARG NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED=BAKED_NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED
|
||||
ARG NEXT_PUBLIC_SOURCEBOT_VERSION=BAKED_NEXT_PUBLIC_SOURCEBOT_VERSION
|
||||
ENV NEXT_PUBLIC_PUBLIC_SEARCH_DEMO=BAKED_NEXT_PUBLIC_PUBLIC_SEARCH_DEMO
|
||||
ENV NEXT_PUBLIC_POSTHOG_PAPIK=BAKED_NEXT_PUBLIC_POSTHOG_PAPIK
|
||||
ENV NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=BAKED_NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
|
||||
ENV NEXT_PUBLIC_SENTRY_ENVIRONMENT=BAKED_NEXT_PUBLIC_SENTRY_ENVIRONMENT
|
||||
ENV NEXT_PUBLIC_SENTRY_WEBAPP_DSN=BAKED_NEXT_PUBLIC_SENTRY_WEBAPP_DSN
|
||||
|
||||
# @nocheckin: This was interfering with the the `matcher` regex in middleware.ts,
|
||||
# causing regular expressions parsing errors when making a request. It's unclear
|
||||
# why exactly this was happening, but it's likely due to a bad replacement happening
|
||||
# in the `sed` command.
|
||||
# @note: leading "/" is required for the basePath property. @see: https://nextjs.org/docs/app/api-reference/next-config-js/basePath
|
||||
# ARG NEXT_PUBLIC_DOMAIN_SUB_PATH=/BAKED_NEXT_PUBLIC_DOMAIN_SUB_PATH
|
||||
|
||||
RUN yarn workspace @sourcebot/web build
|
||||
ENV DOCKER_BUILD=0
|
||||
# ------------------------------
|
||||
|
||||
# ------ Build Backend ------
|
||||
FROM node-alpine AS backend-builder
|
||||
ENV DOCKER_BUILD=1
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json yarn.lock* ./
|
||||
|
|
@ -75,10 +91,22 @@ COPY --from=shared-libs-builder /app/packages/crypto ./packages/crypto
|
|||
COPY --from=shared-libs-builder /app/packages/error ./packages/error
|
||||
RUN yarn workspace @sourcebot/backend install --frozen-lockfile
|
||||
RUN yarn workspace @sourcebot/backend build
|
||||
|
||||
ENV DOCKER_BUILD=0
|
||||
# ------------------------------
|
||||
|
||||
# ------ Runner ------
|
||||
FROM node-alpine AS runner
|
||||
# -----------
|
||||
# Global args
|
||||
ARG SOURCEBOT_VERSION
|
||||
ENV SOURCEBOT_VERSION=$SOURCEBOT_VERSION
|
||||
ARG POSTHOG_PAPIK
|
||||
ENV POSTHOG_PAPIK=$POSTHOG_PAPIK
|
||||
# Local args
|
||||
# -----------
|
||||
|
||||
RUN echo "Sourcebot Version: $SOURCEBOT_VERSION"
|
||||
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV=production
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
|
@ -90,14 +118,6 @@ ENV DATABASE_URL="postgresql://postgres@localhost:5432/sourcebot"
|
|||
ENV REDIS_URL="redis://localhost:6379"
|
||||
ENV SRC_TENANT_ENFORCEMENT_MODE=strict
|
||||
|
||||
ARG SOURCEBOT_VERSION=unknown
|
||||
ENV SOURCEBOT_VERSION=$SOURCEBOT_VERSION
|
||||
RUN echo "Sourcebot Version: $SOURCEBOT_VERSION"
|
||||
|
||||
ARG PUBLIC_SEARCH_DEMO=false
|
||||
ENV PUBLIC_SEARCH_DEMO=$PUBLIC_SEARCH_DEMO
|
||||
RUN echo "Public Search Demo: $PUBLIC_SEARCH_DEMO"
|
||||
|
||||
# Valid values are: debug, info, warn, error
|
||||
ENV SOURCEBOT_LOG_LEVEL=info
|
||||
|
||||
|
|
@ -106,18 +126,9 @@ ENV SOURCEBOT_LOG_LEVEL=info
|
|||
# will serve from http(s)://example.com/sb
|
||||
ENV DOMAIN_SUB_PATH=/
|
||||
|
||||
# PAPIK = Project API Key
|
||||
# Note that this key does not need to be kept secret, so it's not
|
||||
# necessary to use Docker build secrets here.
|
||||
# @see: https://posthog.com/tutorials/api-capture-events#authenticating-with-the-project-api-key
|
||||
ARG POSTHOG_PAPIK=
|
||||
ENV POSTHOG_PAPIK=$POSTHOG_PAPIK
|
||||
|
||||
# Sourcebot collects anonymous usage data using [PostHog](https://posthog.com/). Uncomment this line to disable.
|
||||
# ENV SOURCEBOT_TELEMETRY_DISABLED=1
|
||||
|
||||
ENV STRIPE_PUBLISHABLE_KEY=""
|
||||
|
||||
# Configure zoekt
|
||||
COPY vendor/zoekt/install-ctags-alpine.sh .
|
||||
RUN ./install-ctags-alpine.sh && rm install-ctags-alpine.sh
|
||||
|
|
@ -179,3 +190,4 @@ EXPOSE 3000
|
|||
ENV PORT=3000
|
||||
ENV HOSTNAME="0.0.0.0"
|
||||
ENTRYPOINT ["/sbin/tini", "--", "./entrypoint.sh"]
|
||||
# ------------------------------
|
||||
|
|
@ -108,102 +108,6 @@ fi
|
|||
echo "{\"version\": \"$SOURCEBOT_VERSION\", \"install_id\": \"$SOURCEBOT_INSTALL_ID\"}" > "$FIRST_RUN_FILE"
|
||||
|
||||
|
||||
# Update NextJs public env variables w/o requiring a rebuild.
|
||||
# @see: https://phase.dev/blog/nextjs-public-runtime-variables/
|
||||
{
|
||||
# Infer NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED if it is not set
|
||||
if [ -z "$NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED" ] && [ ! -z "$SOURCEBOT_TELEMETRY_DISABLED" ]; then
|
||||
export NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED="$SOURCEBOT_TELEMETRY_DISABLED"
|
||||
fi
|
||||
|
||||
# Infer NEXT_PUBLIC_SOURCEBOT_VERSION if it is not set
|
||||
if [ -z "$NEXT_PUBLIC_SOURCEBOT_VERSION" ] && [ ! -z "$SOURCEBOT_VERSION" ]; then
|
||||
export NEXT_PUBLIC_SOURCEBOT_VERSION="$SOURCEBOT_VERSION"
|
||||
fi
|
||||
|
||||
# Infer NEXT_PUBLIC_PUBLIC_SEARCH_DEMO if it is not set
|
||||
if [ -z "$NEXT_PUBLIC_PUBLIC_SEARCH_DEMO" ] && [ ! -z "$PUBLIC_SEARCH_DEMO" ]; then
|
||||
export NEXT_PUBLIC_PUBLIC_SEARCH_DEMO="$PUBLIC_SEARCH_DEMO"
|
||||
fi
|
||||
|
||||
# Always infer NEXT_PUBLIC_POSTHOG_PAPIK
|
||||
export NEXT_PUBLIC_POSTHOG_PAPIK="$POSTHOG_PAPIK"
|
||||
|
||||
# Always infer NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
|
||||
export NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="$STRIPE_PUBLISHABLE_KEY"
|
||||
|
||||
# Always infer NEXT_PUBLIC_SENTRY_ENVIRONMENT
|
||||
export NEXT_PUBLIC_SENTRY_ENVIRONMENT="$SENTRY_ENVIRONMENT"
|
||||
|
||||
# Always infer NEXT_PUBLIC_SENTRY_WEBAPP_DSN
|
||||
export NEXT_PUBLIC_SENTRY_WEBAPP_DSN="$SENTRY_WEBAPP_DSN"
|
||||
|
||||
# Iterate over all .js files in .next & public, making substitutions for the `BAKED_` sentinal values
|
||||
# with their actual desired runtime value.
|
||||
find /app/packages/web/public /app/packages/web/.next -type f -name "*.js" |
|
||||
while read file; do
|
||||
sed -i "s|BAKED_NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED|${NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED}|g" "$file"
|
||||
sed -i "s|BAKED_NEXT_PUBLIC_SOURCEBOT_VERSION|${NEXT_PUBLIC_SOURCEBOT_VERSION}|g" "$file"
|
||||
sed -i "s|BAKED_NEXT_PUBLIC_POSTHOG_PAPIK|${NEXT_PUBLIC_POSTHOG_PAPIK}|g" "$file"
|
||||
sed -i "s|BAKED_NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY|${NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY}|g" "$file"
|
||||
sed -i "s|BAKED_NEXT_PUBLIC_SENTRY_ENVIRONMENT|${NEXT_PUBLIC_SENTRY_ENVIRONMENT}|g" "$file"
|
||||
sed -i "s|BAKED_NEXT_PUBLIC_SENTRY_WEBAPP_DSN|${NEXT_PUBLIC_SENTRY_WEBAPP_DSN}|g" "$file"
|
||||
sed -i "s|BAKED_NEXT_PUBLIC_PUBLIC_SEARCH_DEMO|${NEXT_PUBLIC_PUBLIC_SEARCH_DEMO}|g" "$file"
|
||||
done
|
||||
}
|
||||
|
||||
# @nocheckin: This was interfering with the the `matcher` regex in middleware.ts,
|
||||
# causing regular expressions parsing errors when making a request. It's unclear
|
||||
# why exactly this was happening, but it's likely due to a bad replacement happening
|
||||
# in the `sed` command.
|
||||
#
|
||||
# # Update specifically NEXT_PUBLIC_DOMAIN_SUB_PATH w/o requiring a rebuild.
|
||||
# # Ultimately, the DOMAIN_SUB_PATH sets the `basePath` param in the next.config.mjs.
|
||||
# # Similar to above, we pass in a `BAKED_` sentinal value into next.config.mjs at build
|
||||
# # time. Unlike above, the `basePath` configuration is set in files other than just javascript
|
||||
# # code (e.g., manifest files, css files, etc.), so this section has subtle differences.
|
||||
# #
|
||||
# # @see: https://nextjs.org/docs/app/api-reference/next-config-js/basePath
|
||||
# # @see: https://phase.dev/blog/nextjs-public-runtime-variables/
|
||||
# {
|
||||
# if [ ! -z "$DOMAIN_SUB_PATH" ]; then
|
||||
# # If the sub-path is "/", this creates problems with certain replacements. For example:
|
||||
# # /BAKED_NEXT_PUBLIC_DOMAIN_SUB_PATH/_next/image -> //_next/image (notice the double slash...)
|
||||
# # To get around this, we default to an empty sub-path, which is the default when no sub-path is defined.
|
||||
# if [ "$DOMAIN_SUB_PATH" = "/" ]; then
|
||||
# DOMAIN_SUB_PATH=""
|
||||
|
||||
# # Otherwise, we need to ensure that the sub-path starts with a slash, since this is a requirement
|
||||
# # for the basePath property. For example, assume DOMAIN_SUB_PATH=/bot, then:
|
||||
# # /BAKED_NEXT_PUBLIC_DOMAIN_SUB_PATH/_next/image -> /bot/_next/image
|
||||
# elif [[ ! "$DOMAIN_SUB_PATH" =~ ^/ ]]; then
|
||||
# DOMAIN_SUB_PATH="/$DOMAIN_SUB_PATH"
|
||||
# fi
|
||||
# fi
|
||||
|
||||
# if [ ! -z "$DOMAIN_SUB_PATH" ]; then
|
||||
# echo -e "\e[34m[Info] DOMAIN_SUB_PATH was set to "$DOMAIN_SUB_PATH". Overriding default path.\e[0m"
|
||||
# fi
|
||||
|
||||
# # Always set NEXT_PUBLIC_DOMAIN_SUB_PATH to DOMAIN_SUB_PATH (even if it is empty!!)
|
||||
# export NEXT_PUBLIC_DOMAIN_SUB_PATH="$DOMAIN_SUB_PATH"
|
||||
|
||||
# # Iterate over _all_ files in the web directory, making substitutions for the `BAKED_` sentinal values
|
||||
# # with their actual desired runtime value.
|
||||
# find /app/packages/web -type f |
|
||||
# while read file; do
|
||||
# # @note: the leading "/" is required here as it is included at build time. See Dockerfile.
|
||||
# sed -i "s|/BAKED_NEXT_PUBLIC_DOMAIN_SUB_PATH|${NEXT_PUBLIC_DOMAIN_SUB_PATH}|g" "$file"
|
||||
# done
|
||||
# }
|
||||
|
||||
# Upload sourcemaps to Sentry
|
||||
# @nocheckin
|
||||
su -c "sentry-cli login --auth-token $SENTRY_AUTH_TOKEN"
|
||||
su -c "sentry-cli sourcemaps inject --org sourcebot --project backend /app/packages/backend/dist"
|
||||
su -c "sentry-cli sourcemaps upload --org sourcebot --project backend /app/packages/backend/dist"
|
||||
|
||||
|
||||
# Start the database and wait for it to be ready before starting any other service
|
||||
if [ "$DATABASE_URL" = "postgresql://postgres@localhost:5432/sourcebot" ]; then
|
||||
su postgres -c "postgres -D $DB_DATA_DIR" &
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
POSTHOG_HOST=https://us.i.posthog.com
|
||||
|
|
@ -32,6 +32,7 @@
|
|||
"@sourcebot/db": "^0.1.0",
|
||||
"@sourcebot/error": "^0.1.0",
|
||||
"@sourcebot/schemas": "^0.1.0",
|
||||
"@t3-oss/env-core": "^0.12.0",
|
||||
"@types/express": "^5.0.0",
|
||||
"argparse": "^2.0.1",
|
||||
"bullmq": "^5.34.10",
|
||||
|
|
@ -47,6 +48,7 @@
|
|||
"prom-client": "^15.1.3",
|
||||
"simple-git": "^3.27.0",
|
||||
"strip-json-comments": "^5.0.1",
|
||||
"winston": "^3.15.0"
|
||||
"winston": "^3.15.0",
|
||||
"zod": "^3.24.2"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
41
packages/backend/src/env.ts
Normal file
41
packages/backend/src/env.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import { createEnv } from "@t3-oss/env-core";
|
||||
import { z } from "zod";
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
dotenv.config({
|
||||
path: './.env',
|
||||
});
|
||||
|
||||
dotenv.config({
|
||||
path: './.env.local',
|
||||
override: true
|
||||
});
|
||||
|
||||
export const env = createEnv({
|
||||
server: {
|
||||
SOURCEBOT_ENCRYPTION_KEY: z.string(),
|
||||
SOURCEBOT_LOG_LEVEL: z.enum(["info", "debug", "warn", "error"]).default("info"),
|
||||
SOURCEBOT_TELEMETRY_DISABLED: z.enum(["true", "false"]).default("false"),
|
||||
SOURCEBOT_INSTALL_ID: z.string().default("unknown"),
|
||||
SOURCEBOT_VERSION: z.string().default("unknown"),
|
||||
|
||||
POSTHOG_PAPIK: z.string().optional(),
|
||||
POSTHOG_HOST: z.string().url().default('https://us.i.posthog.com'),
|
||||
|
||||
FALLBACK_GITHUB_TOKEN: z.string().optional(),
|
||||
FALLBACK_GITLAB_TOKEN: z.string().optional(),
|
||||
FALLBACK_GITEA_TOKEN: z.string().optional(),
|
||||
|
||||
REDIS_URL: z.string().url().optional().default("redis://localhost:6379"),
|
||||
|
||||
SENTRY_BACKEND_DSN: z.string().optional(),
|
||||
SENTRY_ENVIRONMENT: z.string().optional(),
|
||||
|
||||
LOGTAIL_TOKEN: z.string().optional(),
|
||||
LOGTAIL_HOST: z.string().url().optional(),
|
||||
},
|
||||
runtimeEnv: process.env,
|
||||
emptyStringAsUndefined: true,
|
||||
// Skip environment variable validation in Docker builds.
|
||||
skipValidation: process.env.DOCKER_BUILD === "1",
|
||||
});
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
import dotenv from 'dotenv';
|
||||
import * as Sentry from "@sentry/node";
|
||||
|
||||
export const getEnv = (env: string | undefined, defaultValue?: string, required?: boolean) => {
|
||||
if (required && !env && !defaultValue) {
|
||||
const e = new Error(`Missing required environment variable: ${env}`);
|
||||
Sentry.captureException(e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
return env ?? defaultValue;
|
||||
}
|
||||
|
||||
export const getEnvBoolean = (env: string | undefined, defaultValue: boolean) => {
|
||||
if (!env) {
|
||||
return defaultValue;
|
||||
}
|
||||
return env === 'true' || env === '1';
|
||||
}
|
||||
|
||||
dotenv.config({
|
||||
path: './.env',
|
||||
});
|
||||
dotenv.config({
|
||||
path: './.env.local',
|
||||
override: true
|
||||
});
|
||||
|
||||
|
||||
export const SOURCEBOT_LOG_LEVEL = getEnv(process.env.SOURCEBOT_LOG_LEVEL, 'info')!;
|
||||
export const SOURCEBOT_TELEMETRY_DISABLED = getEnvBoolean(process.env.SOURCEBOT_TELEMETRY_DISABLED, false)!;
|
||||
export const SOURCEBOT_INSTALL_ID = getEnv(process.env.SOURCEBOT_INSTALL_ID, 'unknown')!;
|
||||
export const SOURCEBOT_VERSION = getEnv(process.env.SOURCEBOT_VERSION, 'unknown')!;
|
||||
export const POSTHOG_PAPIK = getEnv(process.env.POSTHOG_PAPIK);
|
||||
export const POSTHOG_HOST = getEnv(process.env.POSTHOG_HOST);
|
||||
|
||||
export const FALLBACK_GITHUB_TOKEN = getEnv(process.env.FALLBACK_GITHUB_TOKEN);
|
||||
export const FALLBACK_GITLAB_TOKEN = getEnv(process.env.FALLBACK_GITLAB_TOKEN);
|
||||
export const FALLBACK_GITEA_TOKEN = getEnv(process.env.FALLBACK_GITEA_TOKEN);
|
||||
|
||||
export const INDEX_CONCURRENCY_MULTIPLE = getEnv(process.env.INDEX_CONCURRENCY_MULTIPLE);
|
||||
export const REDIS_URL = getEnv(process.env.REDIS_URL, 'redis://localhost:6379')!;
|
||||
|
||||
export const SENTRY_BACKEND_DSN = getEnv(process.env.SENTRY_BACKEND_DSN);
|
||||
export const SENTRY_ENVIRONMENT = getEnv(process.env.SENTRY_ENVIRONMENT, 'unknown')!;
|
||||
|
||||
export const LOGTAIL_TOKEN = getEnv(process.env.LOGTAIL_TOKEN);
|
||||
export const LOGTAIL_HOST = getEnv(process.env.LOGTAIL_HOST);
|
||||
|
|
@ -5,15 +5,15 @@ import fetch from 'cross-fetch';
|
|||
import { createLogger } from './logger.js';
|
||||
import micromatch from 'micromatch';
|
||||
import { PrismaClient } from '@sourcebot/db';
|
||||
import { FALLBACK_GITEA_TOKEN } from './environment.js';
|
||||
import { processPromiseResults, throwIfAnyFailed } from './connectionUtils.js';
|
||||
import * as Sentry from "@sentry/node";
|
||||
import { env } from './env.js';
|
||||
|
||||
const logger = createLogger('Gitea');
|
||||
|
||||
export const getGiteaReposFromConfig = async (config: GiteaConnectionConfig, orgId: number, db: PrismaClient) => {
|
||||
const tokenResult = config.token ? await getTokenFromConfig(config.token, orgId, db) : undefined;
|
||||
const token = tokenResult?.token ?? FALLBACK_GITEA_TOKEN;
|
||||
const token = tokenResult?.token ?? env.FALLBACK_GITEA_TOKEN;
|
||||
|
||||
const api = giteaApi(config.url ?? 'https://gitea.com', {
|
||||
token: token,
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ import { createLogger } from "./logger.js";
|
|||
import { getTokenFromConfig, measure, fetchWithRetry } from "./utils.js";
|
||||
import micromatch from "micromatch";
|
||||
import { PrismaClient } from "@sourcebot/db";
|
||||
import { FALLBACK_GITHUB_TOKEN } from "./environment.js";
|
||||
import { BackendException, BackendError } from "@sourcebot/error";
|
||||
import { processPromiseResults, throwIfAnyFailed } from "./connectionUtils.js";
|
||||
import * as Sentry from "@sentry/node";
|
||||
import { env } from "./env.js";
|
||||
|
||||
const logger = createLogger("GitHub");
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ export const getGitHubReposFromConfig = async (config: GithubConnectionConfig, o
|
|||
const secretKey = tokenResult?.secretKey;
|
||||
|
||||
const octokit = new Octokit({
|
||||
auth: token ?? FALLBACK_GITHUB_TOKEN,
|
||||
auth: token ?? env.FALLBACK_GITHUB_TOKEN,
|
||||
...(config.url ? {
|
||||
baseUrl: `${config.url}/api/v3`
|
||||
} : {}),
|
||||
|
|
|
|||
|
|
@ -4,16 +4,16 @@ import { createLogger } from "./logger.js";
|
|||
import { GitlabConnectionConfig } from "@sourcebot/schemas/v3/gitlab.type"
|
||||
import { getTokenFromConfig, measure, fetchWithRetry } from "./utils.js";
|
||||
import { PrismaClient } from "@sourcebot/db";
|
||||
import { FALLBACK_GITLAB_TOKEN } from "./environment.js";
|
||||
import { processPromiseResults, throwIfAnyFailed } from "./connectionUtils.js";
|
||||
import * as Sentry from "@sentry/node";
|
||||
import { env } from "./env.js";
|
||||
|
||||
const logger = createLogger("GitLab");
|
||||
export const GITLAB_CLOUD_HOSTNAME = "gitlab.com";
|
||||
|
||||
export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, orgId: number, db: PrismaClient) => {
|
||||
const tokenResult = config.token ? await getTokenFromConfig(config.token, orgId, db) : undefined;
|
||||
const token = tokenResult?.token ?? FALLBACK_GITLAB_TOKEN;
|
||||
const token = tokenResult?.token ?? env.FALLBACK_GITLAB_TOKEN;
|
||||
|
||||
const api = new Gitlab({
|
||||
...(token ? {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import * as Sentry from "@sentry/node";
|
||||
import { SOURCEBOT_VERSION, SENTRY_BACKEND_DSN, SENTRY_ENVIRONMENT } from "./environment.js";
|
||||
import { env } from "./env.js";
|
||||
|
||||
Sentry.init({
|
||||
dsn: SENTRY_BACKEND_DSN,
|
||||
release: SOURCEBOT_VERSION,
|
||||
environment: SENTRY_ENVIRONMENT,
|
||||
dsn: env.SENTRY_BACKEND_DSN,
|
||||
release: env.SOURCEBOT_VERSION,
|
||||
environment: env.SENTRY_ENVIRONMENT,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import winston, { format } from 'winston';
|
||||
import { SOURCEBOT_LOG_LEVEL, LOGTAIL_TOKEN, LOGTAIL_HOST } from './environment.js';
|
||||
import { Logtail } from '@logtail/node';
|
||||
import { LogtailTransport } from '@logtail/winston';
|
||||
import { env } from './env.js';
|
||||
|
||||
const { combine, colorize, timestamp, prettyPrint, errors, printf, label: labelFn } = format;
|
||||
|
||||
|
||||
const createLogger = (label: string) => {
|
||||
return winston.createLogger({
|
||||
level: SOURCEBOT_LOG_LEVEL,
|
||||
level: env.SOURCEBOT_LOG_LEVEL,
|
||||
format: combine(
|
||||
errors({ stack: true }),
|
||||
timestamp(),
|
||||
|
|
@ -31,10 +31,10 @@ const createLogger = (label: string) => {
|
|||
}),
|
||||
),
|
||||
}),
|
||||
...(LOGTAIL_TOKEN && LOGTAIL_HOST ? [
|
||||
...(env.LOGTAIL_TOKEN && env.LOGTAIL_HOST ? [
|
||||
new LogtailTransport(
|
||||
new Logtail(LOGTAIL_TOKEN, {
|
||||
endpoint: LOGTAIL_HOST,
|
||||
new Logtail(env.LOGTAIL_TOKEN, {
|
||||
endpoint: env.LOGTAIL_HOST,
|
||||
})
|
||||
)
|
||||
] : []),
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@ import { DEFAULT_SETTINGS } from './constants.js';
|
|||
import { Redis } from 'ioredis';
|
||||
import { ConnectionManager } from './connectionManager.js';
|
||||
import { RepoManager } from './repoManager.js';
|
||||
import { INDEX_CONCURRENCY_MULTIPLE, REDIS_URL } from './environment.js';
|
||||
import { env } from './env.js';
|
||||
import { PromClient } from './promClient.js';
|
||||
|
||||
const logger = createLogger('main');
|
||||
|
||||
export const main = async (db: PrismaClient, context: AppContext) => {
|
||||
const redis = new Redis(REDIS_URL, {
|
||||
const redis = new Redis(env.REDIS_URL, {
|
||||
maxRetriesPerRequest: null
|
||||
});
|
||||
redis.ping().then(() => {
|
||||
|
|
@ -23,8 +23,8 @@ export const main = async (db: PrismaClient, context: AppContext) => {
|
|||
});
|
||||
|
||||
const settings = DEFAULT_SETTINGS;
|
||||
if (INDEX_CONCURRENCY_MULTIPLE) {
|
||||
settings.indexConcurrencyMultiple = parseInt(INDEX_CONCURRENCY_MULTIPLE);
|
||||
if (env.INDEX_CONCURRENCY_MULTIPLE) {
|
||||
settings.indexConcurrencyMultiple = env.INDEX_CONCURRENCY_MULTIPLE;
|
||||
}
|
||||
|
||||
const promClient = new PromClient();
|
||||
|
|
|
|||
|
|
@ -1,29 +1,29 @@
|
|||
import { PostHog } from 'posthog-node';
|
||||
import { PosthogEvent, PosthogEventMap } from './posthogEvents.js';
|
||||
import { POSTHOG_HOST, POSTHOG_PAPIK, SOURCEBOT_INSTALL_ID, SOURCEBOT_TELEMETRY_DISABLED, SOURCEBOT_VERSION } from './environment.js';
|
||||
import { env } from './env.js';
|
||||
|
||||
let posthog: PostHog | undefined = undefined;
|
||||
|
||||
if (POSTHOG_PAPIK) {
|
||||
if (env.POSTHOG_PAPIK) {
|
||||
posthog = new PostHog(
|
||||
POSTHOG_PAPIK,
|
||||
env.POSTHOG_PAPIK,
|
||||
{
|
||||
host: POSTHOG_HOST,
|
||||
host: env.POSTHOG_HOST,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function captureEvent<E extends PosthogEvent>(event: E, properties: PosthogEventMap[E]) {
|
||||
if (SOURCEBOT_TELEMETRY_DISABLED) {
|
||||
if (env.SOURCEBOT_TELEMETRY_DISABLED) {
|
||||
return;
|
||||
}
|
||||
|
||||
posthog?.capture({
|
||||
distinctId: SOURCEBOT_INSTALL_ID,
|
||||
distinctId: env.SOURCEBOT_INSTALL_ID,
|
||||
event: event,
|
||||
properties: {
|
||||
...properties,
|
||||
sourcebot_version: SOURCEBOT_VERSION,
|
||||
sourcebot_version: env.SOURCEBOT_VERSION,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
||||
NEXT_PUBLIC_POSTHOG_ASSET_HOST=https://us-assets.i.posthog.com
|
||||
NEXT_PUBLIC_POSTHOG_UI_HOST=https://us.posthog.com
|
||||
|
|
@ -1,22 +1,30 @@
|
|||
import {withSentryConfig} from "@sentry/nextjs";
|
||||
await import("./src/env.mjs");
|
||||
import { withSentryConfig } from "@sentry/nextjs";
|
||||
import { env } from "./src/env.mjs";
|
||||
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
output: "standalone",
|
||||
|
||||
// This is required when using standalone builds.
|
||||
// @see: https://env.t3.gg/docs/nextjs#create-your-schema
|
||||
transpilePackages: ["@t3-oss/env-nextjs", "@t3-oss/env-core"],
|
||||
|
||||
// @see : https://posthog.com/docs/advanced/proxy/nextjs
|
||||
async rewrites() {
|
||||
return [
|
||||
{
|
||||
source: "/ingest/static/:path*",
|
||||
destination: `${process.env.NEXT_PUBLIC_POSTHOG_ASSET_HOST}/static/:path*`,
|
||||
destination: `${env.NEXT_PUBLIC_POSTHOG_ASSET_HOST}/static/:path*`,
|
||||
},
|
||||
{
|
||||
source: "/ingest/:path*",
|
||||
destination: `${process.env.NEXT_PUBLIC_POSTHOG_HOST}/:path*`,
|
||||
destination: `${env.NEXT_PUBLIC_POSTHOG_HOST}/:path*`,
|
||||
},
|
||||
{
|
||||
source: "/ingest/decide",
|
||||
destination: `${process.env.NEXT_PUBLIC_POSTHOG_HOST}/decide`,
|
||||
destination: `${env.NEXT_PUBLIC_POSTHOG_HOST}/decide`,
|
||||
},
|
||||
];
|
||||
},
|
||||
|
|
@ -30,51 +38,42 @@ const nextConfig = {
|
|||
hostname: '**',
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
// @nocheckin: This was interfering with the the `matcher` regex in middleware.ts,
|
||||
// causing regular expressions parsing errors when making a request. It's unclear
|
||||
// why exactly this was happening, but it's likely due to a bad replacement happening
|
||||
// in the `sed` command.
|
||||
// @note: this is evaluated at build time.
|
||||
// ...(process.env.NEXT_PUBLIC_DOMAIN_SUB_PATH ? {
|
||||
// basePath: process.env.NEXT_PUBLIC_DOMAIN_SUB_PATH,
|
||||
// } : {})
|
||||
},
|
||||
};
|
||||
|
||||
export default withSentryConfig(nextConfig, {
|
||||
// For all available options, see:
|
||||
// https://www.npmjs.com/package/@sentry/webpack-plugin#options
|
||||
// For all available options, see:
|
||||
// https://www.npmjs.com/package/@sentry/webpack-plugin#options
|
||||
|
||||
org: "sourcebot",
|
||||
project: "webapp",
|
||||
org: "sourcebot",
|
||||
project: "webapp",
|
||||
|
||||
// Only print logs for uploading source maps in CI
|
||||
silent: !process.env.CI,
|
||||
// Only print logs for uploading source maps in CI
|
||||
silent: !process.env.CI,
|
||||
|
||||
// For all available options, see:
|
||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
|
||||
// For all available options, see:
|
||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
|
||||
|
||||
// Upload a larger set of source maps for prettier stack traces (increases build time)
|
||||
widenClientFileUpload: true,
|
||||
// Upload a larger set of source maps for prettier stack traces (increases build time)
|
||||
widenClientFileUpload: true,
|
||||
|
||||
// Automatically annotate React components to show their full name in breadcrumbs and session replay
|
||||
reactComponentAnnotation: {
|
||||
enabled: true,
|
||||
},
|
||||
// Automatically annotate React components to show their full name in breadcrumbs and session replay
|
||||
reactComponentAnnotation: {
|
||||
enabled: true,
|
||||
},
|
||||
|
||||
// Route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers.
|
||||
// This can increase your server load as well as your hosting bill.
|
||||
// Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client-
|
||||
// side errors will fail.
|
||||
tunnelRoute: "/monitoring",
|
||||
// Route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers.
|
||||
// This can increase your server load as well as your hosting bill.
|
||||
// Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client-
|
||||
// side errors will fail.
|
||||
tunnelRoute: "/monitoring",
|
||||
|
||||
// Automatically tree-shake Sentry logger statements to reduce bundle size
|
||||
disableLogger: true,
|
||||
// Automatically tree-shake Sentry logger statements to reduce bundle size
|
||||
disableLogger: true,
|
||||
|
||||
// Enables automatic instrumentation of Vercel Cron Monitors. (Does not yet work with App Router route handlers.)
|
||||
// See the following for more information:
|
||||
// https://docs.sentry.io/product/crons/
|
||||
// https://vercel.com/docs/cron-jobs
|
||||
automaticVercelMonitors: true,
|
||||
// Enables automatic instrumentation of Vercel Cron Monitors. (Does not yet work with App Router route handlers.)
|
||||
// See the following for more information:
|
||||
// https://docs.sentry.io/product/crons/
|
||||
// https://vercel.com/docs/cron-jobs
|
||||
automaticVercelMonitors: true,
|
||||
});
|
||||
|
|
@ -75,6 +75,7 @@
|
|||
"@ssddanbrown/codemirror-lang-twig": "^1.0.0",
|
||||
"@stripe/react-stripe-js": "^3.1.1",
|
||||
"@stripe/stripe-js": "^5.6.0",
|
||||
"@t3-oss/env-nextjs": "^0.12.0",
|
||||
"@tanstack/react-query": "^5.53.3",
|
||||
"@tanstack/react-table": "^8.20.5",
|
||||
"@tanstack/react-virtual": "^3.10.8",
|
||||
|
|
@ -133,7 +134,7 @@
|
|||
"tailwind-scrollbar-hide": "^1.1.7",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"usehooks-ts": "^3.1.0",
|
||||
"zod": "^3.23.8"
|
||||
"zod": "^3.24.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bcrypt": "^5.0.2",
|
||||
|
|
|
|||
|
|
@ -16,10 +16,9 @@ import { decrypt, encrypt } from "@sourcebot/crypto"
|
|||
import { getConnection } from "./data/connection";
|
||||
import { ConnectionSyncStatus, Prisma, OrgRole, RepoIndexingStatus, StripeSubscriptionStatus } from "@sourcebot/db";
|
||||
import { cookies, headers } from "next/headers"
|
||||
import { getStripe } from "@/lib/stripe"
|
||||
import { getUser } from "@/data/user";
|
||||
import { Session } from "next-auth";
|
||||
import { STRIPE_PRODUCT_ID, CONFIG_MAX_REPOS_NO_TOKEN, EMAIL_FROM, SMTP_CONNECTION_URL, AUTH_URL } from "@/lib/environment";
|
||||
import { env } from "@/env.mjs";
|
||||
import Stripe from "stripe";
|
||||
import { render } from "@react-email/components";
|
||||
import InviteUserEmail from "./emails/inviteUserEmail";
|
||||
|
|
@ -27,6 +26,7 @@ import { createTransport } from "nodemailer";
|
|||
import { orgDomainSchema, orgNameSchema, repositoryQuerySchema } from "./lib/schemas";
|
||||
import { RepositoryQuery } from "./lib/types";
|
||||
import { MOBILE_UNSUPPORTED_SPLASH_SCREEN_DISMISSED_COOKIE_NAME } from "./lib/constants";
|
||||
import { stripeClient } from "./lib/stripe";
|
||||
|
||||
const ajv = new Ajv({
|
||||
validateFormats: false,
|
||||
|
|
@ -594,7 +594,7 @@ export const createInvites = async (emails: string[], domain: string): Promise<{
|
|||
});
|
||||
|
||||
// Send invites to recipients
|
||||
if (SMTP_CONNECTION_URL && EMAIL_FROM) {
|
||||
if (env.SMTP_CONNECTION_URL && env.EMAIL_FROM) {
|
||||
const origin = (await headers()).get('origin')!;
|
||||
await Promise.all(emails.map(async (email) => {
|
||||
const invite = await prisma.invite.findUnique({
|
||||
|
|
@ -619,9 +619,9 @@ export const createInvites = async (emails: string[], domain: string): Promise<{
|
|||
},
|
||||
});
|
||||
const inviteLink = `${origin}/redeem?invite_id=${invite.id}`;
|
||||
const transport = createTransport(SMTP_CONNECTION_URL);
|
||||
const transport = createTransport(env.SMTP_CONNECTION_URL);
|
||||
const html = await render(InviteUserEmail({
|
||||
baseUrl: AUTH_URL,
|
||||
baseUrl: env.AUTH_URL,
|
||||
host: {
|
||||
name: session.user.name ?? undefined,
|
||||
email: session.user.email!,
|
||||
|
|
@ -637,7 +637,7 @@ export const createInvites = async (emails: string[], domain: string): Promise<{
|
|||
|
||||
const result = await transport.sendMail({
|
||||
to: email,
|
||||
from: EMAIL_FROM,
|
||||
from: env.EMAIL_FROM,
|
||||
subject: `Join ${invite.org.name} on Sourcebot`,
|
||||
html,
|
||||
text: `Join ${invite.org.name} on Sourcebot by clicking here: ${inviteLink}`,
|
||||
|
|
@ -718,8 +718,7 @@ export const redeemInvite = async (inviteId: string): Promise<{ success: boolean
|
|||
const existingSeatCount = subscription.items.data[0].quantity;
|
||||
const newSeatCount = (existingSeatCount || 1) + 1
|
||||
|
||||
const stripe = getStripe();
|
||||
await stripe.subscriptionItems.update(
|
||||
await stripeClient?.subscriptionItems.update(
|
||||
subscription.items.data[0].id,
|
||||
{
|
||||
quantity: newSeatCount,
|
||||
|
|
@ -873,10 +872,16 @@ export const createOnboardingSubscription = async (domain: string) =>
|
|||
return notFound();
|
||||
}
|
||||
|
||||
const stripe = getStripe();
|
||||
if (!stripeClient) {
|
||||
return {
|
||||
statusCode: StatusCodes.INTERNAL_SERVER_ERROR,
|
||||
errorCode: ErrorCode.STRIPE_CLIENT_NOT_INITIALIZED,
|
||||
message: "Stripe client is not initialized.",
|
||||
} satisfies ServiceError;
|
||||
}
|
||||
|
||||
// @nocheckin
|
||||
const test_clock = AUTH_URL !== "https://app.sourcebot.dev" ? await stripe.testHelpers.testClocks.create({
|
||||
const test_clock = env.AUTH_URL !== "https://app.sourcebot.dev" ? await stripeClient.testHelpers.testClocks.create({
|
||||
frozen_time: Math.floor(Date.now() / 1000)
|
||||
}) : null;
|
||||
|
||||
|
|
@ -886,7 +891,7 @@ export const createOnboardingSubscription = async (domain: string) =>
|
|||
return org.stripeCustomerId;
|
||||
}
|
||||
|
||||
const customer = await stripe.customers.create({
|
||||
const customer = await stripeClient.customers.create({
|
||||
name: org.name,
|
||||
email: user.email ?? undefined,
|
||||
test_clock: test_clock?.id,
|
||||
|
|
@ -915,13 +920,13 @@ export const createOnboardingSubscription = async (domain: string) =>
|
|||
}
|
||||
|
||||
|
||||
const prices = await stripe.prices.list({
|
||||
product: STRIPE_PRODUCT_ID,
|
||||
const prices = await stripeClient.prices.list({
|
||||
product: env.STRIPE_PRODUCT_ID,
|
||||
expand: ['data.product'],
|
||||
});
|
||||
|
||||
try {
|
||||
const subscription = await stripe.subscriptions.create({
|
||||
const subscription = await stripeClient.subscriptions.create({
|
||||
customer: customerId,
|
||||
items: [{
|
||||
price: prices.data[0].id,
|
||||
|
|
@ -974,6 +979,14 @@ export const createStripeCheckoutSession = async (domain: string) =>
|
|||
return notFound();
|
||||
}
|
||||
|
||||
if (!stripeClient) {
|
||||
return {
|
||||
statusCode: StatusCodes.INTERNAL_SERVER_ERROR,
|
||||
errorCode: ErrorCode.STRIPE_CLIENT_NOT_INITIALIZED,
|
||||
message: "Stripe client is not initialized.",
|
||||
} satisfies ServiceError;
|
||||
}
|
||||
|
||||
const orgMembers = await prisma.userToOrg.findMany({
|
||||
where: {
|
||||
orgId,
|
||||
|
|
@ -984,14 +997,13 @@ export const createStripeCheckoutSession = async (domain: string) =>
|
|||
});
|
||||
const numOrgMembers = orgMembers.length;
|
||||
|
||||
const stripe = getStripe();
|
||||
const origin = (await headers()).get('origin')
|
||||
const prices = await stripe.prices.list({
|
||||
product: STRIPE_PRODUCT_ID,
|
||||
const prices = await stripeClient.prices.list({
|
||||
product: env.STRIPE_PRODUCT_ID,
|
||||
expand: ['data.product'],
|
||||
});
|
||||
|
||||
const stripeSession = await stripe.checkout.sessions.create({
|
||||
const stripeSession = await stripeClient.checkout.sessions.create({
|
||||
customer: org.stripeCustomerId as string,
|
||||
payment_method_types: ['card'],
|
||||
line_items: [
|
||||
|
|
@ -1033,9 +1045,16 @@ export const getCustomerPortalSessionLink = async (domain: string): Promise<stri
|
|||
return notFound();
|
||||
}
|
||||
|
||||
const stripe = getStripe();
|
||||
if (!stripeClient) {
|
||||
return {
|
||||
statusCode: StatusCodes.INTERNAL_SERVER_ERROR,
|
||||
errorCode: ErrorCode.STRIPE_CLIENT_NOT_INITIALIZED,
|
||||
message: "Stripe client is not initialized.",
|
||||
} satisfies ServiceError;
|
||||
}
|
||||
|
||||
const origin = (await headers()).get('origin')
|
||||
const portalSession = await stripe.billingPortal.sessions.create({
|
||||
const portalSession = await stripeClient.billingPortal.sessions.create({
|
||||
customer: org.stripeCustomerId as string,
|
||||
return_url: `${origin}/${domain}/settings/billing`,
|
||||
});
|
||||
|
|
@ -1064,8 +1083,15 @@ export const getSubscriptionBillingEmail = async (domain: string): Promise<strin
|
|||
return notFound();
|
||||
}
|
||||
|
||||
const stripe = getStripe();
|
||||
const customer = await stripe.customers.retrieve(org.stripeCustomerId);
|
||||
if (!stripeClient) {
|
||||
return {
|
||||
statusCode: StatusCodes.INTERNAL_SERVER_ERROR,
|
||||
errorCode: ErrorCode.STRIPE_CLIENT_NOT_INITIALIZED,
|
||||
message: "Stripe client is not initialized.",
|
||||
} satisfies ServiceError;
|
||||
}
|
||||
|
||||
const customer = await stripeClient.customers.retrieve(org.stripeCustomerId);
|
||||
if (!('email' in customer) || customer.deleted) {
|
||||
return notFound();
|
||||
}
|
||||
|
|
@ -1086,8 +1112,15 @@ export const changeSubscriptionBillingEmail = async (domain: string, newEmail: s
|
|||
return notFound();
|
||||
}
|
||||
|
||||
const stripe = getStripe();
|
||||
await stripe.customers.update(org.stripeCustomerId, {
|
||||
if (!stripeClient) {
|
||||
return {
|
||||
statusCode: StatusCodes.INTERNAL_SERVER_ERROR,
|
||||
errorCode: ErrorCode.STRIPE_CLIENT_NOT_INITIALIZED,
|
||||
message: "Stripe client is not initialized.",
|
||||
} satisfies ServiceError;
|
||||
}
|
||||
|
||||
await stripeClient.customers.update(org.stripeCustomerId, {
|
||||
email: newEmail,
|
||||
});
|
||||
|
||||
|
|
@ -1143,8 +1176,7 @@ export const removeMemberFromOrg = async (memberId: string, domain: string): Pro
|
|||
const existingSeatCount = subscription.items.data[0].quantity;
|
||||
const newSeatCount = (existingSeatCount || 1) - 1;
|
||||
|
||||
const stripe = getStripe();
|
||||
await stripe.subscriptionItems.update(
|
||||
await stripeClient?.subscriptionItems.update(
|
||||
subscription.items.data[0].id,
|
||||
{
|
||||
quantity: newSeatCount,
|
||||
|
|
@ -1198,8 +1230,7 @@ export const leaveOrg = async (domain: string): Promise<{ success: boolean } | S
|
|||
const existingSeatCount = subscription.items.data[0].quantity;
|
||||
const newSeatCount = (existingSeatCount || 1) - 1;
|
||||
|
||||
const stripe = getStripe();
|
||||
await stripe.subscriptionItems.update(
|
||||
await stripeClient?.subscriptionItems.update(
|
||||
subscription.items.data[0].id,
|
||||
{
|
||||
quantity: newSeatCount,
|
||||
|
|
@ -1307,8 +1338,15 @@ const _fetchSubscriptionForOrg = async (orgId: number, prisma: Prisma.Transactio
|
|||
return notFound();
|
||||
}
|
||||
|
||||
const stripe = getStripe();
|
||||
const subscriptions = await stripe.subscriptions.list({
|
||||
if (!stripeClient) {
|
||||
return {
|
||||
statusCode: StatusCodes.INTERNAL_SERVER_ERROR,
|
||||
errorCode: ErrorCode.STRIPE_CLIENT_NOT_INITIALIZED,
|
||||
message: "Stripe client is not initialized.",
|
||||
} satisfies ServiceError;
|
||||
}
|
||||
|
||||
const subscriptions = await stripeClient.subscriptions.list({
|
||||
customer: org.stripeCustomerId
|
||||
});
|
||||
|
||||
|
|
@ -1389,11 +1427,11 @@ const parseConnectionConfig = (connectionType: string, config: string) => {
|
|||
}
|
||||
})();
|
||||
|
||||
if (!hasToken && numRepos && numRepos > CONFIG_MAX_REPOS_NO_TOKEN) {
|
||||
if (!hasToken && numRepos && numRepos > env.CONFIG_MAX_REPOS_NO_TOKEN) {
|
||||
return {
|
||||
statusCode: StatusCodes.BAD_REQUEST,
|
||||
errorCode: ErrorCode.INVALID_REQUEST_BODY,
|
||||
message: `You must provide a token to sync more than ${CONFIG_MAX_REPOS_NO_TOKEN} repositories.`,
|
||||
message: `You must provide a token to sync more than ${env.CONFIG_MAX_REPOS_NO_TOKEN} repositories.`,
|
||||
} satisfies ServiceError;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { CircleXIcon } from "lucide-react";
|
|||
import { useDomain } from "@/hooks/useDomain";
|
||||
import { unwrapServiceError } from "@/lib/utils";
|
||||
import useCaptureEvent from "@/hooks/useCaptureEvent";
|
||||
import { NEXT_PUBLIC_POLLING_INTERVAL_MS } from "@/lib/environment.client";
|
||||
import { env } from "@/env.mjs";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { ConnectionSyncStatus, RepoIndexingStatus } from "@sourcebot/db";
|
||||
import { getConnections } from "@/actions";
|
||||
|
|
@ -20,14 +20,14 @@ export const ErrorNavIndicator = () => {
|
|||
queryKey: ['repos', domain],
|
||||
queryFn: () => unwrapServiceError(getRepos(domain)),
|
||||
select: (data) => data.filter(repo => repo.repoIndexingStatus === RepoIndexingStatus.FAILED),
|
||||
refetchInterval: NEXT_PUBLIC_POLLING_INTERVAL_MS,
|
||||
refetchInterval: env.NEXT_PUBLIC_POLLING_INTERVAL_MS,
|
||||
});
|
||||
|
||||
const { data: connections, isPending: isPendingConnections, isError: isErrorConnections } = useQuery({
|
||||
queryKey: ['connections', domain],
|
||||
queryFn: () => unwrapServiceError(getConnections(domain)),
|
||||
select: (data) => data.filter(connection => connection.syncStatus === ConnectionSyncStatus.FAILED),
|
||||
refetchInterval: NEXT_PUBLIC_POLLING_INTERVAL_MS,
|
||||
refetchInterval: env.NEXT_PUBLIC_POLLING_INTERVAL_MS,
|
||||
});
|
||||
|
||||
if (isPendingRepos || isErrorRepos || isPendingConnections || isErrorConnections) {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { getRepos } from "@/actions";
|
|||
import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card";
|
||||
import useCaptureEvent from "@/hooks/useCaptureEvent";
|
||||
import { useDomain } from "@/hooks/useDomain";
|
||||
import { NEXT_PUBLIC_POLLING_INTERVAL_MS } from "@/lib/environment.client";
|
||||
import { env } from "@/env.mjs";
|
||||
import { unwrapServiceError } from "@/lib/utils";
|
||||
import { RepoIndexingStatus } from "@prisma/client";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
|
@ -19,7 +19,7 @@ export const ProgressNavIndicator = () => {
|
|||
queryKey: ['repos', domain],
|
||||
queryFn: () => unwrapServiceError(getRepos(domain)),
|
||||
select: (data) => data.filter(repo => repo.repoIndexingStatus === RepoIndexingStatus.IN_INDEX_QUEUE || repo.repoIndexingStatus === RepoIndexingStatus.INDEXING),
|
||||
refetchInterval: NEXT_PUBLIC_POLLING_INTERVAL_MS,
|
||||
refetchInterval: env.NEXT_PUBLIC_POLLING_INTERVAL_MS,
|
||||
});
|
||||
|
||||
if (isPending || isError || inProgressRepos.length === 0) {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { useDomain } from "@/hooks/useDomain";
|
|||
import { useQuery } from "@tanstack/react-query";
|
||||
import { unwrapServiceError } from "@/lib/utils";
|
||||
import { getRepos } from "@/actions";
|
||||
import { NEXT_PUBLIC_POLLING_INTERVAL_MS } from "@/lib/environment.client";
|
||||
import { env } from "@/env.mjs";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import {
|
||||
Carousel,
|
||||
|
|
@ -22,7 +22,7 @@ export function RepositorySnapshot() {
|
|||
const { data: repos, isPending, isError } = useQuery({
|
||||
queryKey: ['repos', domain],
|
||||
queryFn: () => unwrapServiceError(getRepos(domain)),
|
||||
refetchInterval: NEXT_PUBLIC_POLLING_INTERVAL_MS,
|
||||
refetchInterval: env.NEXT_PUBLIC_POLLING_INTERVAL_MS,
|
||||
});
|
||||
|
||||
if (isPending || isError || !repos) {
|
||||
|
|
|
|||
|
|
@ -28,11 +28,10 @@ import { useMemo } from "react"
|
|||
import { KeymapType } from "@/lib/types"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { useKeymapType } from "@/hooks/useKeymapType"
|
||||
import { NEXT_PUBLIC_SOURCEBOT_VERSION } from "@/lib/environment.client";
|
||||
import { useSession } from "next-auth/react";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { signOut } from "next-auth/react"
|
||||
|
||||
import { env } from "@/env.mjs";
|
||||
|
||||
interface SettingsDropdownProps {
|
||||
menuButtonClassName?: string;
|
||||
|
|
@ -147,7 +146,7 @@ export const SettingsDropdown = ({
|
|||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<div className="px-2 py-1 text-sm text-muted-foreground">
|
||||
version: {NEXT_PUBLIC_SOURCEBOT_VERSION}
|
||||
version: {env.NEXT_PUBLIC_SOURCEBOT_VERSION}
|
||||
</div>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { useDomain } from "@/hooks/useDomain";
|
|||
import { getConnections } from "@/actions";
|
||||
import { unwrapServiceError } from "@/lib/utils";
|
||||
import useCaptureEvent from "@/hooks/useCaptureEvent";
|
||||
import { NEXT_PUBLIC_POLLING_INTERVAL_MS } from "@/lib/environment.client";
|
||||
import { env } from "@/env.mjs";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { ConnectionSyncStatus } from "@prisma/client";
|
||||
|
||||
|
|
@ -19,7 +19,7 @@ export const WarningNavIndicator = () => {
|
|||
queryKey: ['connections', domain],
|
||||
queryFn: () => unwrapServiceError(getConnections(domain)),
|
||||
select: (data) => data.filter(connection => connection.syncStatus === ConnectionSyncStatus.SYNCED_WITH_WARNINGS),
|
||||
refetchInterval: NEXT_PUBLIC_POLLING_INTERVAL_MS,
|
||||
refetchInterval: env.NEXT_PUBLIC_POLLING_INTERVAL_MS,
|
||||
});
|
||||
|
||||
if (isPending || isError || connections.length === 0) {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { useCallback } from "react";
|
|||
import { useQuery } from "@tanstack/react-query";
|
||||
import { flagConnectionForSync, getConnectionInfo } from "@/actions";
|
||||
import { isServiceError, unwrapServiceError } from "@/lib/utils";
|
||||
import { NEXT_PUBLIC_POLLING_INTERVAL_MS } from "@/lib/environment.client";
|
||||
import { env } from "@/env.mjs";
|
||||
import { ConnectionSyncStatus } from "@sourcebot/db";
|
||||
import { FiLoader } from "react-icons/fi";
|
||||
import { CircleCheckIcon, AlertTriangle, CircleXIcon } from "lucide-react";
|
||||
|
|
@ -31,7 +31,7 @@ export const Overview = ({ connectionId }: OverviewProps) => {
|
|||
const { data: connection, isPending, error, refetch } = useQuery({
|
||||
queryKey: ['connection', domain, connectionId],
|
||||
queryFn: () => unwrapServiceError(getConnectionInfo(connectionId, domain)),
|
||||
refetchInterval: NEXT_PUBLIC_POLLING_INTERVAL_MS,
|
||||
refetchInterval: env.NEXT_PUBLIC_POLLING_INTERVAL_MS,
|
||||
});
|
||||
|
||||
const handleSecretsNavigation = useCallback(() => {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import { Search, Loader2 } from "lucide-react";
|
|||
import { Input } from "@/components/ui/input";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { RepoListItemSkeleton } from "./repoListItemSkeleton";
|
||||
import { NEXT_PUBLIC_POLLING_INTERVAL_MS } from "@/lib/environment.client";
|
||||
import { env } from "@/env.mjs";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { MultiSelect } from "@/components/ui/multi-select";
|
||||
|
|
@ -78,7 +78,7 @@ export const RepoList = ({ connectionId }: RepoListProps) => {
|
|||
return new Date(a.indexedAt ?? new Date()).getTime() - new Date(b.indexedAt ?? new Date()).getTime();
|
||||
});
|
||||
},
|
||||
refetchInterval: NEXT_PUBLIC_POLLING_INTERVAL_MS,
|
||||
refetchInterval: env.NEXT_PUBLIC_POLLING_INTERVAL_MS,
|
||||
});
|
||||
|
||||
const { data: connection, isPending: isConnectionPending, error: isConnectionError } = useQuery({
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { InfoCircledIcon } from "@radix-ui/react-icons";
|
|||
import { getConnections } from "@/actions";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { NEXT_PUBLIC_POLLING_INTERVAL_MS } from "@/lib/environment.client";
|
||||
import { env } from "@/env.mjs";
|
||||
import { RepoIndexingStatus, ConnectionSyncStatus } from "@sourcebot/db";
|
||||
import { Search } from "lucide-react";
|
||||
import { Input } from "@/components/ui/input";
|
||||
|
|
@ -43,7 +43,7 @@ export const ConnectionList = ({
|
|||
const { data: unfilteredConnections, isPending, error } = useQuery({
|
||||
queryKey: ['connections', domain],
|
||||
queryFn: () => unwrapServiceError(getConnections(domain)),
|
||||
refetchInterval: NEXT_PUBLIC_POLLING_INTERVAL_MS,
|
||||
refetchInterval: env.NEXT_PUBLIC_POLLING_INTERVAL_MS,
|
||||
});
|
||||
|
||||
const connections = useMemo(() => {
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@ import { columns, RepositoryColumnInfo } from "./columns";
|
|||
import { unwrapServiceError } from "@/lib/utils";
|
||||
import { getRepos } from "@/actions";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { NEXT_PUBLIC_POLLING_INTERVAL_MS } from "@/lib/environment.client";
|
||||
import { useDomain } from "@/hooks/useDomain";
|
||||
import { RepoIndexingStatus } from "@sourcebot/db";
|
||||
import { useMemo } from "react";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { env } from "@/env.mjs";
|
||||
|
||||
export const RepositoryTable = () => {
|
||||
const domain = useDomain();
|
||||
|
|
@ -19,7 +19,7 @@ export const RepositoryTable = () => {
|
|||
queryFn: async () => {
|
||||
return await unwrapServiceError(getRepos(domain));
|
||||
},
|
||||
refetchInterval: NEXT_PUBLIC_POLLING_INTERVAL_MS,
|
||||
refetchInterval: env.NEXT_PUBLIC_POLLING_INTERVAL_MS,
|
||||
refetchIntervalInBackground: true,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import { CodePreviewPanel } from "./components/codePreviewPanel";
|
|||
import { FilterPanel } from "./components/filterPanel";
|
||||
import { SearchResultsPanel } from "./components/searchResultsPanel";
|
||||
import { useDomain } from "@/hooks/useDomain";
|
||||
import { NEXT_PUBLIC_PUBLIC_SEARCH_DEMO } from "@/lib/environment.client";
|
||||
|
||||
const DEFAULT_MAX_MATCH_DISPLAY_COUNT = 10000;
|
||||
|
||||
|
|
@ -105,7 +104,6 @@ const SearchPageInternal = () => {
|
|||
const fileLanguages = searchResponse.Result.Files?.map(file => file.Language) || [];
|
||||
|
||||
captureEvent("search_finished", {
|
||||
query: NEXT_PUBLIC_PUBLIC_SEARCH_DEMO ? searchQuery : null, // @nocheckin
|
||||
contentBytesLoaded: searchResponse.Result.ContentBytesLoaded,
|
||||
indexBytesLoaded: searchResponse.Result.IndexBytesLoaded,
|
||||
crashes: searchResponse.Result.Crashes,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { isServiceError } from "@/lib/utils";
|
|||
import { getCurrentUserRole } from "@/actions";
|
||||
import { getOrgFromDomain } from "@/data/org";
|
||||
import { ChangeOrgDomainCard } from "./components/changeOrgDomainCard";
|
||||
import { SOURCEBOT_ROOT_DOMAIN } from "@/lib/environment";
|
||||
import { env } from "@/env.mjs";
|
||||
|
||||
interface GeneralSettingsPageProps {
|
||||
params: {
|
||||
|
|
@ -42,7 +42,7 @@ export default async function GeneralSettingsPage({ params: { domain } }: Genera
|
|||
<ChangeOrgDomainCard
|
||||
orgDomain={org.domain}
|
||||
currentUserRole={currentUserRole}
|
||||
rootDomain={SOURCEBOT_ROOT_DOMAIN}
|
||||
rootDomain={env.SOURCEBOT_ROOT_DOMAIN}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import { OrgRole } from "@sourcebot/db";
|
||||
import { resolveServerPath } from "@/app/api/(client)/client";
|
||||
import { useToast } from "@/components/hooks/use-toast";
|
||||
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog";
|
||||
import { Avatar, AvatarImage } from "@/components/ui/avatar";
|
||||
|
|
@ -124,8 +123,7 @@ export const InvitesList = ({ invites, currentUserRole }: InviteListProps) => {
|
|||
className="gap-2"
|
||||
title="Copy invite link"
|
||||
onClick={() => {
|
||||
const basePath = `${window.location.origin}${resolveServerPath('/')}`;
|
||||
const url = createPathWithQueryParams(`${basePath}redeem?invite_id=${invite.id}`);
|
||||
const url = createPathWithQueryParams(`${window.location.origin}/redeem?invite_id=${invite.id}`);
|
||||
navigator.clipboard.writeText(url)
|
||||
.then(() => {
|
||||
toast({
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
'use client';
|
||||
|
||||
import { NEXT_PUBLIC_DOMAIN_SUB_PATH } from "@/lib/environment.client";
|
||||
import { fileSourceResponseSchema, getVersionResponseSchema, listRepositoriesResponseSchema, searchResponseSchema } from "@/lib/schemas";
|
||||
import { FileSourceRequest, FileSourceResponse, GetVersionResponse, ListRepositoriesResponse, SearchRequest, SearchResponse } from "@/lib/types";
|
||||
import assert from "assert";
|
||||
|
||||
export const search = async (body: SearchRequest, domain: string): Promise<SearchResponse> => {
|
||||
const path = resolveServerPath("/api/search");
|
||||
const result = await fetch(path, {
|
||||
const result = await fetch("/api/search", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
|
|
@ -20,8 +18,7 @@ export const search = async (body: SearchRequest, domain: string): Promise<Searc
|
|||
}
|
||||
|
||||
export const fetchFileSource = async (body: FileSourceRequest, domain: string): Promise<FileSourceResponse> => {
|
||||
const path = resolveServerPath("/api/source");
|
||||
const result = await fetch(path, {
|
||||
const result = await fetch("/api/source", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
|
|
@ -34,8 +31,7 @@ export const fetchFileSource = async (body: FileSourceRequest, domain: string):
|
|||
}
|
||||
|
||||
export const getRepos = async (domain: string): Promise<ListRepositoriesResponse> => {
|
||||
const path = resolveServerPath("/api/repos");
|
||||
const result = await fetch(path, {
|
||||
const result = await fetch("/api/repos", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
|
|
@ -47,8 +43,7 @@ export const getRepos = async (domain: string): Promise<ListRepositoriesResponse
|
|||
}
|
||||
|
||||
export const getVersion = async (): Promise<GetVersionResponse> => {
|
||||
const path = resolveServerPath("/api/version");
|
||||
const result = await fetch(path, {
|
||||
const result = await fetch("/api/version", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
|
|
@ -56,13 +51,3 @@ export const getVersion = async (): Promise<GetVersionResponse> => {
|
|||
}).then(response => response.json());
|
||||
return getVersionResponseSchema.parse(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a subpath to a api route on the server (e.g., /api/search),
|
||||
* returns the full path to that route on the server, taking into account
|
||||
* the base path (if any).
|
||||
*/
|
||||
export const resolveServerPath = (path: string) => {
|
||||
assert(path.startsWith("/"));
|
||||
return `${NEXT_PUBLIC_DOMAIN_SUB_PATH}${path}`;
|
||||
}
|
||||
|
|
@ -2,9 +2,9 @@ import { headers } from 'next/headers';
|
|||
import { NextRequest } from 'next/server';
|
||||
import Stripe from 'stripe';
|
||||
import { prisma } from '@/prisma';
|
||||
import { STRIPE_WEBHOOK_SECRET } from '@/lib/environment';
|
||||
import { getStripe } from '@/lib/stripe';
|
||||
import { ConnectionSyncStatus, StripeSubscriptionStatus } from '@sourcebot/db';
|
||||
import { stripeClient } from '@/lib/stripe';
|
||||
import { env } from '@/env.mjs';
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
const body = await req.text();
|
||||
|
|
@ -14,12 +14,19 @@ export async function POST(req: NextRequest) {
|
|||
return new Response('No signature', { status: 400 });
|
||||
}
|
||||
|
||||
if (!stripeClient) {
|
||||
return new Response('Stripe client not initialized', { status: 500 });
|
||||
}
|
||||
|
||||
if (!env.STRIPE_WEBHOOK_SECRET) {
|
||||
return new Response('Stripe webhook secret not set', { status: 500 });
|
||||
}
|
||||
|
||||
try {
|
||||
const stripe = getStripe();
|
||||
const event = stripe.webhooks.constructEvent(
|
||||
const event = stripeClient.webhooks.constructEvent(
|
||||
body,
|
||||
signature,
|
||||
STRIPE_WEBHOOK_SECRET!
|
||||
env.STRIPE_WEBHOOK_SECRET
|
||||
);
|
||||
|
||||
if (event.type === 'customer.subscription.deleted') {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { SOURCEBOT_VERSION } from "@/lib/environment";
|
||||
import { env } from "@/env.mjs";
|
||||
import { GetVersionResponse } from "@/lib/types";
|
||||
|
||||
// Note: In Next.JS 14, GET methods with no params are cached by default at build time.
|
||||
|
|
@ -10,6 +10,6 @@ export const dynamic = "force-dynamic";
|
|||
|
||||
export const GET = async () => {
|
||||
return Response.json({
|
||||
version: SOURCEBOT_VERSION,
|
||||
version: env.NEXT_PUBLIC_SOURCEBOT_VERSION,
|
||||
} satisfies GetVersionResponse);
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import { PostHogProvider } from "./posthogProvider";
|
|||
import { Toaster } from "@/components/ui/toaster";
|
||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||
import { SessionProvider } from "next-auth/react";
|
||||
import { env } from "@/env.mjs";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Sourcebot",
|
||||
|
|
@ -26,7 +27,7 @@ export default function RootLayout({
|
|||
<body>
|
||||
<Toaster />
|
||||
<SessionProvider>
|
||||
<PostHogProvider>
|
||||
<PostHogProvider disabled={env.SOURCEBOT_TELEMETRY_DISABLED === "true"}>
|
||||
<ThemeProvider
|
||||
attribute="class"
|
||||
defaultTheme="system"
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { redirect } from "next/navigation";
|
|||
import { OnboardHeader } from "./components/onboardHeader";
|
||||
import { OnboardingSteps } from "@/lib/constants";
|
||||
import { LogoutEscapeHatch } from "../components/logoutEscapeHatch";
|
||||
import { SOURCEBOT_ROOT_DOMAIN } from "@/lib/environment";
|
||||
import { env } from "@/env.mjs";
|
||||
|
||||
export default async function Onboarding() {
|
||||
const session = await auth();
|
||||
|
|
@ -19,7 +19,7 @@ export default async function Onboarding() {
|
|||
description="Create a organization for your team to search and share code across your repositories."
|
||||
step={OnboardingSteps.CreateOrg}
|
||||
/>
|
||||
<OrgCreateForm rootDomain={SOURCEBOT_ROOT_DOMAIN} />
|
||||
<OrgCreateForm rootDomain={env.SOURCEBOT_ROOT_DOMAIN} />
|
||||
<LogoutEscapeHatch className="absolute top-0 right-0 p-4 sm:p-12" />
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,14 +1,10 @@
|
|||
'use client'
|
||||
import { NEXT_PUBLIC_POSTHOG_PAPIK, NEXT_PUBLIC_POSTHOG_UI_HOST, NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED, NEXT_PUBLIC_PUBLIC_SEARCH_DEMO } from '@/lib/environment.client'
|
||||
import posthog from 'posthog-js'
|
||||
import { usePostHog } from 'posthog-js/react'
|
||||
import { PostHogProvider as PHProvider } from 'posthog-js/react'
|
||||
import { resolveServerPath } from './api/(client)/client'
|
||||
import { isDefined } from '@/lib/utils'
|
||||
import { usePathname, useSearchParams } from "next/navigation"
|
||||
import { useEffect, Suspense } from "react"
|
||||
|
||||
const POSTHOG_ENABLED = isDefined(NEXT_PUBLIC_POSTHOG_PAPIK) && !NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED;
|
||||
import { Suspense, useEffect } from "react"
|
||||
import { env } from '@/env.mjs'
|
||||
|
||||
function PostHogPageView() {
|
||||
const pathname = usePathname()
|
||||
|
|
@ -30,26 +26,23 @@ function PostHogPageView() {
|
|||
return null
|
||||
}
|
||||
|
||||
export default function SuspendedPostHogPageView() {
|
||||
return <Suspense fallback={null}>
|
||||
<PostHogPageView />
|
||||
</Suspense>
|
||||
interface PostHogProviderProps {
|
||||
children: React.ReactNode
|
||||
disabled: boolean
|
||||
}
|
||||
|
||||
export function PostHogProvider({ children }: { children: React.ReactNode }) {
|
||||
export function PostHogProvider({ children, disabled }: PostHogProviderProps) {
|
||||
useEffect(() => {
|
||||
if (POSTHOG_ENABLED) {
|
||||
if (!disabled && env.NEXT_PUBLIC_POSTHOG_PAPIK) {
|
||||
posthog.init(env.NEXT_PUBLIC_POSTHOG_PAPIK, {
|
||||
// @see next.config.mjs for path rewrites to the "/ingest" route.
|
||||
const posthogHostPath = resolveServerPath('/ingest');
|
||||
|
||||
posthog.init(NEXT_PUBLIC_POSTHOG_PAPIK!, {
|
||||
api_host: posthogHostPath,
|
||||
ui_host: NEXT_PUBLIC_POSTHOG_UI_HOST,
|
||||
api_host: "/ingest",
|
||||
ui_host: env.NEXT_PUBLIC_POSTHOG_UI_HOST,
|
||||
person_profiles: 'identified_only',
|
||||
capture_pageview: NEXT_PUBLIC_PUBLIC_SEARCH_DEMO, // @nocheckin Disable automatic pageview capture if we're not in public demo mode
|
||||
capture_pageview: false, // @nocheckin Disable automatic pageview capture if we're not in public demo mode
|
||||
autocapture: false, // Disable automatic event capture
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
sanitize_properties: !NEXT_PUBLIC_PUBLIC_SEARCH_DEMO ? (properties: Record<string, any>, _event: string) => {
|
||||
sanitize_properties: (properties: Record<string, any>, _event: string) => {
|
||||
// https://posthog.com/docs/libraries/js#config
|
||||
if (properties['$current_url']) {
|
||||
properties['$current_url'] = null;
|
||||
|
|
@ -59,16 +52,18 @@ export function PostHogProvider({ children }: { children: React.ReactNode }) {
|
|||
}
|
||||
|
||||
return properties;
|
||||
} : undefined
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log("PostHog telemetry disabled");
|
||||
}
|
||||
}, [])
|
||||
}, [disabled])
|
||||
|
||||
return (
|
||||
<PHProvider client={posthog}>
|
||||
<SuspendedPostHogPageView />
|
||||
<Suspense fallback={null}>
|
||||
<PostHogPageView />
|
||||
</Suspense>
|
||||
{children}
|
||||
</PHProvider>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -6,17 +6,7 @@ import Credentials from "next-auth/providers/credentials"
|
|||
import EmailProvider from "next-auth/providers/nodemailer";
|
||||
import { PrismaAdapter } from "@auth/prisma-adapter"
|
||||
import { prisma } from "@/prisma";
|
||||
import {
|
||||
AUTH_GITHUB_CLIENT_ID,
|
||||
AUTH_GITHUB_CLIENT_SECRET,
|
||||
AUTH_GOOGLE_CLIENT_ID,
|
||||
AUTH_GOOGLE_CLIENT_SECRET,
|
||||
AUTH_SECRET,
|
||||
AUTH_URL,
|
||||
AUTH_CREDENTIALS_LOGIN_ENABLED,
|
||||
EMAIL_FROM,
|
||||
SMTP_CONNECTION_URL
|
||||
} from "./lib/environment";
|
||||
import { env } from "@/env.mjs";
|
||||
import { User } from '@sourcebot/db';
|
||||
import 'next-auth/jwt';
|
||||
import type { Provider } from "next-auth/providers";
|
||||
|
|
@ -44,24 +34,24 @@ declare module 'next-auth/jwt' {
|
|||
export const getProviders = () => {
|
||||
const providers: Provider[] = [];
|
||||
|
||||
if (AUTH_GITHUB_CLIENT_ID && AUTH_GITHUB_CLIENT_SECRET) {
|
||||
if (env.AUTH_GITHUB_CLIENT_ID && env.AUTH_GITHUB_CLIENT_SECRET) {
|
||||
providers.push(GitHub({
|
||||
clientId: AUTH_GITHUB_CLIENT_ID,
|
||||
clientSecret: AUTH_GITHUB_CLIENT_SECRET,
|
||||
clientId: env.AUTH_GITHUB_CLIENT_ID,
|
||||
clientSecret: env.AUTH_GITHUB_CLIENT_SECRET,
|
||||
}));
|
||||
}
|
||||
|
||||
if (AUTH_GOOGLE_CLIENT_ID && AUTH_GOOGLE_CLIENT_SECRET) {
|
||||
if (env.AUTH_GOOGLE_CLIENT_ID && env.AUTH_GOOGLE_CLIENT_SECRET) {
|
||||
providers.push(Google({
|
||||
clientId: AUTH_GOOGLE_CLIENT_ID,
|
||||
clientSecret: AUTH_GOOGLE_CLIENT_SECRET,
|
||||
clientId: env.AUTH_GOOGLE_CLIENT_ID,
|
||||
clientSecret: env.AUTH_GOOGLE_CLIENT_SECRET,
|
||||
}));
|
||||
}
|
||||
|
||||
if (SMTP_CONNECTION_URL && EMAIL_FROM) {
|
||||
if (env.SMTP_CONNECTION_URL && env.EMAIL_FROM) {
|
||||
providers.push(EmailProvider({
|
||||
server: SMTP_CONNECTION_URL,
|
||||
from: EMAIL_FROM,
|
||||
server: env.SMTP_CONNECTION_URL,
|
||||
from: env.EMAIL_FROM,
|
||||
maxAge: 60 * 10,
|
||||
generateVerificationToken: async () => {
|
||||
const token = String(Math.floor(100000 + Math.random() * 900000));
|
||||
|
|
@ -69,7 +59,7 @@ export const getProviders = () => {
|
|||
},
|
||||
sendVerificationRequest: async ({ identifier, provider, token }) => {
|
||||
const transport = createTransport(provider.server);
|
||||
const html = await render(MagicLinkEmail({ baseUrl: AUTH_URL, token: token }));
|
||||
const html = await render(MagicLinkEmail({ baseUrl: env.AUTH_URL, token: token }));
|
||||
const result = await transport.sendMail({
|
||||
to: identifier,
|
||||
from: provider.from,
|
||||
|
|
@ -86,7 +76,7 @@ export const getProviders = () => {
|
|||
}));
|
||||
}
|
||||
|
||||
if (AUTH_CREDENTIALS_LOGIN_ENABLED) {
|
||||
if (env.AUTH_CREDENTIALS_LOGIN_ENABLED) {
|
||||
providers.push(Credentials({
|
||||
credentials: {
|
||||
email: {},
|
||||
|
|
@ -102,7 +92,7 @@ export const getProviders = () => {
|
|||
|
||||
// authorize runs in the edge runtime (where we cannot make DB calls / access environment variables),
|
||||
// so we need to make a request to the server to verify the credentials.
|
||||
const response = await fetch(new URL('/api/auth/verifyCredentials', AUTH_URL), {
|
||||
const response = await fetch(new URL('/api/auth/verifyCredentials', env.AUTH_URL), {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ email, password }),
|
||||
});
|
||||
|
|
@ -125,11 +115,11 @@ export const getProviders = () => {
|
|||
return providers;
|
||||
}
|
||||
|
||||
const useSecureCookies = AUTH_URL?.startsWith("https://") ?? false;
|
||||
const hostName = AUTH_URL ? new URL(AUTH_URL).hostname : "localhost";
|
||||
const useSecureCookies = env.AUTH_URL?.startsWith("https://") ?? false;
|
||||
const hostName = env.AUTH_URL ? new URL(env.AUTH_URL).hostname : "localhost";
|
||||
|
||||
export const { handlers, signIn, signOut, auth } = NextAuth({
|
||||
secret: AUTH_SECRET,
|
||||
secret: env.AUTH_SECRET,
|
||||
adapter: PrismaAdapter(prisma),
|
||||
session: {
|
||||
strategy: "jwt",
|
||||
|
|
|
|||
60
packages/web/src/env.mjs
Normal file
60
packages/web/src/env.mjs
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
import { createEnv } from "@t3-oss/env-nextjs";
|
||||
import { z } from "zod";
|
||||
|
||||
export const env = createEnv({
|
||||
server: {
|
||||
// Zoekt
|
||||
ZOEKT_WEBSERVER_URL: z.string().url().default('http://localhost:6070'),
|
||||
SHARD_MAX_MATCH_COUNT: z.number().default(10000),
|
||||
TOTAL_MAX_MATCH_COUNT: z.number().default(100000),
|
||||
|
||||
// Auth
|
||||
AUTH_SECRET: z.string(),
|
||||
AUTH_GITHUB_CLIENT_ID: z.string().optional(),
|
||||
AUTH_GITHUB_CLIENT_SECRET: z.string().optional(),
|
||||
AUTH_GOOGLE_CLIENT_ID: z.string().optional(),
|
||||
AUTH_GOOGLE_CLIENT_SECRET: z.string().optional(),
|
||||
AUTH_URL: z.string().url(),
|
||||
AUTH_CREDENTIALS_LOGIN_ENABLED: z.boolean().default(true),
|
||||
|
||||
// Email
|
||||
SMTP_CONNECTION_URL: z.string().url().optional(),
|
||||
EMAIL_FROM: z.string().email().optional(),
|
||||
|
||||
// Stripe
|
||||
STRIPE_SECRET_KEY: z.string().optional(),
|
||||
STRIPE_PRODUCT_ID: z.string().optional(),
|
||||
STRIPE_WEBHOOK_SECRET: z.string().optional(),
|
||||
|
||||
// Misc
|
||||
CONFIG_MAX_REPOS_NO_TOKEN: z.number().default(500),
|
||||
SOURCEBOT_ROOT_DOMAIN: z.string().default("localhost:3000"),
|
||||
NODE_ENV: z.enum(["development", "test", "production"]),
|
||||
SOURCEBOT_TELEMETRY_DISABLED: z.enum(["true", "false"]).default("false"),
|
||||
},
|
||||
// @NOTE: Make sure you destructure all client variables in the
|
||||
// `experimental__runtimeEnv` block below.
|
||||
client: {
|
||||
// PostHog
|
||||
NEXT_PUBLIC_POSTHOG_PAPIK: z.string().optional(),
|
||||
NEXT_PUBLIC_POSTHOG_HOST: z.string().url().default('https://us.i.posthog.com'),
|
||||
NEXT_PUBLIC_POSTHOG_ASSET_HOST: z.string().url().default('https://us-assets.i.posthog.com'),
|
||||
NEXT_PUBLIC_POSTHOG_UI_HOST: z.string().url().default('https://us.posthog.com'),
|
||||
|
||||
// Misc
|
||||
NEXT_PUBLIC_SOURCEBOT_VERSION: z.string().default('unknown'),
|
||||
NEXT_PUBLIC_POLLING_INTERVAL_MS: z.number().default(5000),
|
||||
},
|
||||
// For Next.js >= 13.4.4, you only need to destructure client variables:
|
||||
experimental__runtimeEnv: {
|
||||
NEXT_PUBLIC_POSTHOG_PAPIK: process.env.NEXT_PUBLIC_POSTHOG_PAPIK,
|
||||
NEXT_PUBLIC_POSTHOG_HOST: process.env.NEXT_PUBLIC_POSTHOG_HOST,
|
||||
NEXT_PUBLIC_POSTHOG_ASSET_HOST: process.env.NEXT_PUBLIC_POSTHOG_ASSET_HOST,
|
||||
NEXT_PUBLIC_POSTHOG_UI_HOST: process.env.NEXT_PUBLIC_POSTHOG_UI_HOST,
|
||||
NEXT_PUBLIC_SOURCEBOT_VERSION: process.env.NEXT_PUBLIC_SOURCEBOT_VERSION,
|
||||
NEXT_PUBLIC_POLLING_INTERVAL_MS: process.env.NEXT_PUBLIC_POLLING_INTERVAL_MS,
|
||||
},
|
||||
// Skip environment variable validation in Docker builds.
|
||||
skipValidation: process.env.DOCKER_BUILD === "1",
|
||||
emptyStringAsUndefined: true,
|
||||
});
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
import { CaptureOptions } from "posthog-js";
|
||||
import posthog from "posthog-js";
|
||||
import { PosthogEvent, PosthogEventMap } from "../lib/posthogEvents";
|
||||
import { NEXT_PUBLIC_SOURCEBOT_VERSION } from "@/lib/environment.client";
|
||||
import { env } from "@/env.mjs";
|
||||
|
||||
export function captureEvent<E extends PosthogEvent>(event: E, properties: PosthogEventMap[E], options?: CaptureOptions) {
|
||||
if(!options) {
|
||||
|
|
@ -12,7 +12,7 @@ export function captureEvent<E extends PosthogEvent>(event: E, properties: Posth
|
|||
options.send_instantly = true;
|
||||
posthog.capture(event, {
|
||||
...properties,
|
||||
sourcebot_version: NEXT_PUBLIC_SOURCEBOT_VERSION,
|
||||
sourcebot_version: env.NEXT_PUBLIC_SOURCEBOT_VERSION,
|
||||
}, options);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
import 'client-only';
|
||||
|
||||
import { getEnv, getEnvBoolean, getEnvNumber } from "./utils";
|
||||
|
||||
export const NEXT_PUBLIC_POSTHOG_PAPIK = getEnv(process.env.NEXT_PUBLIC_POSTHOG_PAPIK);
|
||||
export const NEXT_PUBLIC_POSTHOG_HOST = getEnv(process.env.NEXT_PUBLIC_POSTHOG_HOST);
|
||||
export const NEXT_PUBLIC_POSTHOG_UI_HOST = getEnv(process.env.NEXT_PUBLIC_POSTHOG_UI_HOST);
|
||||
export const NEXT_PUBLIC_POSTHOG_ASSET_HOST = getEnv(process.env.NEXT_PUBLIC_POSTHOG_ASSET_HOST);
|
||||
export const NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED = getEnvBoolean(process.env.NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED, false);
|
||||
export const NEXT_PUBLIC_SOURCEBOT_VERSION = getEnv(process.env.NEXT_PUBLIC_SOURCEBOT_VERSION, "unknown")!;
|
||||
export const NEXT_PUBLIC_DOMAIN_SUB_PATH = getEnv(process.env.NEXT_PUBLIC_DOMAIN_SUB_PATH, "")!;
|
||||
export const NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY = getEnv(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY);
|
||||
export const NEXT_PUBLIC_POLLING_INTERVAL_MS = getEnvNumber(process.env.NEXT_PUBLIC_POLLING_INTERVAL_MS, 5000);
|
||||
export const NEXT_PUBLIC_PUBLIC_SEARCH_DEMO = getEnvBoolean(process.env.NEXT_PUBLIC_PUBLIC_SEARCH_DEMO, false);
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
import 'server-only';
|
||||
|
||||
import { getEnv, getEnvBoolean, getEnvNumber } from "./utils";
|
||||
|
||||
export const ZOEKT_WEBSERVER_URL = getEnv(process.env.ZOEKT_WEBSERVER_URL, "http://localhost:6070")!;
|
||||
export const SHARD_MAX_MATCH_COUNT = getEnvNumber(process.env.SHARD_MAX_MATCH_COUNT, 10000);
|
||||
export const TOTAL_MAX_MATCH_COUNT = getEnvNumber(process.env.TOTAL_MAX_MATCH_COUNT, 100000);
|
||||
export const SOURCEBOT_VERSION = getEnv(process.env.SOURCEBOT_VERSION, 'unknown')!;
|
||||
export const NODE_ENV = process.env.NODE_ENV;
|
||||
|
||||
export const AUTH_SECRET = getEnv(process.env.AUTH_SECRET); // Generate using `npx auth secret`
|
||||
export const AUTH_GITHUB_CLIENT_ID = getEnv(process.env.AUTH_GITHUB_CLIENT_ID);
|
||||
export const AUTH_GITHUB_CLIENT_SECRET = getEnv(process.env.AUTH_GITHUB_CLIENT_SECRET);
|
||||
export const AUTH_GOOGLE_CLIENT_ID = getEnv(process.env.AUTH_GOOGLE_CLIENT_ID);
|
||||
export const AUTH_GOOGLE_CLIENT_SECRET = getEnv(process.env.AUTH_GOOGLE_CLIENT_SECRET);
|
||||
export const AUTH_URL = getEnv(process.env.AUTH_URL)!;
|
||||
export const AUTH_CREDENTIALS_LOGIN_ENABLED = getEnvBoolean(process.env.AUTH_CREDENTIALS_LOGIN_ENABLED, true);
|
||||
|
||||
export const STRIPE_SECRET_KEY = getEnv(process.env.STRIPE_SECRET_KEY);
|
||||
export const STRIPE_PRODUCT_ID = getEnv(process.env.STRIPE_PRODUCT_ID);
|
||||
export const STRIPE_WEBHOOK_SECRET = getEnv(process.env.STRIPE_WEBHOOK_SECRET);
|
||||
|
||||
export const CONFIG_MAX_REPOS_NO_TOKEN = getEnvNumber(process.env.CONFIG_MAX_REPOS_NO_TOKEN, 500);
|
||||
|
||||
export const SMTP_CONNECTION_URL = getEnv(process.env.SMTP_CONNECTION_URL);
|
||||
export const EMAIL_FROM = getEnv(process.env.EMAIL_FROM);
|
||||
|
||||
export const SOURCEBOT_ROOT_DOMAIN = getEnv(process.env.SOURCEBOT_ROOT_DOMAIN, "localhost:3000")!;
|
||||
export const PUBLIC_SEARCH_DEMO = getEnvBoolean(process.env.PUBLIC_SEARCH_DEMO, false);
|
||||
|
|
@ -19,4 +19,5 @@ export enum ErrorCode {
|
|||
STRIPE_CHECKOUT_ERROR = 'STRIPE_CHECKOUT_ERROR',
|
||||
SECRET_ALREADY_EXISTS = 'SECRET_ALREADY_EXISTS',
|
||||
SUBSCRIPTION_ALREADY_EXISTS = 'SUBSCRIPTION_ALREADY_EXISTS',
|
||||
STRIPE_CLIENT_NOT_INITIALIZED = 'STRIPE_CLIENT_NOT_INITIALIZED',
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
export type PosthogEventMap = {
|
||||
search_finished: {
|
||||
query: string | null,
|
||||
contentBytesLoaded: number,
|
||||
indexBytesLoaded: number,
|
||||
crashes: number,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import escapeStringRegexp from "escape-string-regexp";
|
||||
import { SHARD_MAX_MATCH_COUNT, TOTAL_MAX_MATCH_COUNT } from "../environment";
|
||||
import { env } from "@/env.mjs";
|
||||
import { listRepositoriesResponseSchema, zoektSearchResponseSchema } from "../schemas";
|
||||
import { FileSourceRequest, FileSourceResponse, ListRepositoriesResponse, SearchRequest, SearchResponse } from "../types";
|
||||
import { fileNotFound, invalidZoektResponse, ServiceError, unexpectedError } from "../serviceError";
|
||||
|
|
@ -59,8 +59,8 @@ export const search = async ({ query, maxMatchDisplayCount, whole}: SearchReques
|
|||
ChunkMatches: true,
|
||||
MaxMatchDisplayCount: maxMatchDisplayCount,
|
||||
Whole: !!whole,
|
||||
ShardMaxMatchCount: SHARD_MAX_MATCH_COUNT,
|
||||
TotalMaxMatchCount: TOTAL_MAX_MATCH_COUNT,
|
||||
ShardMaxMatchCount: env.SHARD_MAX_MATCH_COUNT,
|
||||
TotalMaxMatchCount: env.TOTAL_MAX_MATCH_COUNT,
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { ZOEKT_WEBSERVER_URL } from "../environment"
|
||||
|
||||
import { env } from "@/env.mjs";
|
||||
|
||||
interface ZoektRequest {
|
||||
path: string,
|
||||
|
|
@ -17,7 +16,7 @@ export const zoektFetch = async ({
|
|||
cache,
|
||||
}: ZoektRequest) => {
|
||||
const response = await fetch(
|
||||
new URL(path, ZOEKT_WEBSERVER_URL),
|
||||
new URL(path, env.ZOEKT_WEBSERVER_URL),
|
||||
{
|
||||
method,
|
||||
headers: {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,8 @@
|
|||
import 'server-only';
|
||||
import { env } from '@/env.mjs'
|
||||
import Stripe from "stripe";
|
||||
|
||||
import Stripe from 'stripe'
|
||||
import { STRIPE_SECRET_KEY } from './environment'
|
||||
|
||||
let stripeInstance: Stripe | null = null;
|
||||
export const getStripe = () => {
|
||||
if (!stripeInstance) {
|
||||
stripeInstance = new Stripe(STRIPE_SECRET_KEY!);
|
||||
}
|
||||
return stripeInstance;
|
||||
}
|
||||
export const stripeClient =
|
||||
env.STRIPE_SECRET_KEY
|
||||
? new Stripe(env.STRIPE_SECRET_KEY)
|
||||
: undefined;
|
||||
|
|
@ -158,30 +158,6 @@ export const isServiceError = (data: unknown): data is ServiceError => {
|
|||
'message' in data;
|
||||
}
|
||||
|
||||
export const getEnv = (env: string | undefined, defaultValue?: string) => {
|
||||
return env ?? defaultValue;
|
||||
}
|
||||
|
||||
export const getEnvNumber = (env: string | undefined, defaultValue: number = 0) => {
|
||||
if (!env) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
const num = Number(env);
|
||||
if (isNaN(num)) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
export const getEnvBoolean = (env: string | undefined, defaultValue: boolean) => {
|
||||
if (!env) {
|
||||
return defaultValue;
|
||||
}
|
||||
return env === 'true' || env === '1';
|
||||
}
|
||||
|
||||
// From https://developer.mozilla.org/en-US/docs/Glossary/Base64#the_unicode_problem
|
||||
export const base64Decode = (base64: string): string => {
|
||||
const binString = atob(base64);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,6 @@
|
|||
"@/public/*": ["./public/*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "src/env.mjs"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
|
|
|||
20
yarn.lock
20
yarn.lock
|
|
@ -3555,6 +3555,18 @@
|
|||
"@swc/counter" "^0.1.3"
|
||||
tslib "^2.4.0"
|
||||
|
||||
"@t3-oss/env-core@0.12.0", "@t3-oss/env-core@^0.12.0":
|
||||
version "0.12.0"
|
||||
resolved "https://registry.npmjs.org/@t3-oss/env-core/-/env-core-0.12.0.tgz#d5b6d92bf07d2f3ccdf59cc428f1faf114350d35"
|
||||
integrity sha512-lOPj8d9nJJTt81mMuN9GMk8x5veOt7q9m11OSnCBJhwp1QrL/qR+M8Y467ULBSm9SunosryWNbmQQbgoiMgcdw==
|
||||
|
||||
"@t3-oss/env-nextjs@^0.12.0":
|
||||
version "0.12.0"
|
||||
resolved "https://registry.npmjs.org/@t3-oss/env-nextjs/-/env-nextjs-0.12.0.tgz#a3a89c5d2eca35c96e8ffd5fe97922873a39c7a0"
|
||||
integrity sha512-rFnvYk1049RnNVUPvY8iQ55AuQh1Rr+qZzQBh3t++RttCGK4COpXGNxS4+45afuQq02lu+QAOy/5955aU8hRKw==
|
||||
dependencies:
|
||||
"@t3-oss/env-core" "0.12.0"
|
||||
|
||||
"@tanstack/query-core@5.59.0":
|
||||
version "5.59.0"
|
||||
resolved "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.59.0.tgz"
|
||||
|
|
@ -10474,10 +10486,10 @@ yocto-queue@^0.1.0:
|
|||
resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz"
|
||||
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
|
||||
|
||||
zod@^3.23.8:
|
||||
version "3.23.8"
|
||||
resolved "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz"
|
||||
integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==
|
||||
zod@^3.24.2:
|
||||
version "3.24.2"
|
||||
resolved "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz#8efa74126287c675e92f46871cfc8d15c34372b3"
|
||||
integrity sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==
|
||||
|
||||
zwitch@^2.0.4:
|
||||
version "2.0.4"
|
||||
|
|
|
|||
Loading…
Reference in a new issue