From a37925906ff37c3b960842d0d2ccd59b0f2239c3 Mon Sep 17 00:00:00 2001
From: bkellam
Date: Wed, 19 Mar 2025 12:00:43 -0700
Subject: [PATCH 1/6] wip on single tenancy mode
---
.env.development | 3 +-
packages/web/src/actions.ts | 70 ++++++++++++++-----
.../[domain]/components/orgSelector/index.tsx | 17 +++--
.../app/[domain]/connections/[id]/page.tsx | 7 --
packages/web/src/app/[domain]/layout.tsx | 35 +++++-----
.../app/[domain]/settings/(general)/page.tsx | 6 --
.../app/[domain]/settings/members/page.tsx | 17 ++---
packages/web/src/data/user.ts | 39 -----------
packages/web/src/env.mjs | 2 +
packages/web/src/initialize.ts | 46 ++++++++++++
packages/web/src/instrumentation.ts | 16 +++--
packages/web/src/lib/constants.ts | 8 ++-
12 files changed, 152 insertions(+), 114 deletions(-)
delete mode 100644 packages/web/src/data/user.ts
create mode 100644 packages/web/src/initialize.ts
diff --git a/.env.development b/.env.development
index aae06e86..d01b5c09 100644
--- a/.env.development
+++ b/.env.development
@@ -80,4 +80,5 @@ SOURCEBOT_TELEMETRY_DISABLED=true # Disables telemetry collection
# CONFIG_MAX_REPOS_NO_TOKEN=
# SOURCEBOT_ROOT_DOMAIN=
-# NODE_ENV=
\ No newline at end of file
+# NODE_ENV=
+# SOURCEBOT_TENANCY_MODE=mutli
\ No newline at end of file
diff --git a/packages/web/src/actions.ts b/packages/web/src/actions.ts
index 122675c7..8ee0024f 100644
--- a/packages/web/src/actions.ts
+++ b/packages/web/src/actions.ts
@@ -16,7 +16,6 @@ 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 { getUser } from "@/data/user";
import { Session } from "next-auth";
import { env } from "@/env.mjs";
import Stripe from "stripe";
@@ -25,7 +24,7 @@ import InviteUserEmail from "./emails/inviteUserEmail";
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 { MOBILE_UNSUPPORTED_SPLASH_SCREEN_DISMISSED_COOKIE_NAME, SINGLE_TENANT_USER_EMAIL, SINGLE_TENANT_USER_ID } from "./lib/constants";
import { stripeClient } from "./lib/stripe";
import { IS_BILLING_ENABLED } from "./lib/stripe";
@@ -34,6 +33,16 @@ const ajv = new Ajv({
});
export const withAuth = async (fn: (session: Session) => Promise) => {
+ if (env.SOURCEBOT_TENANCY_MODE === 'single') {
+ return fn({
+ user: {
+ id: SINGLE_TENANT_USER_ID,
+ email: SINGLE_TENANT_USER_EMAIL,
+ },
+ expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 30).toISOString(),
+ });
+ }
+
const session = await auth();
if (!session) {
return notAuthenticated();
@@ -89,11 +98,6 @@ export const withOrgMembership = async (session: Session, domain: string, fn:
});
}
-export const isAuthed = async () => {
- const session = await auth();
- return session != null;
-}
-
export const createOrg = (name: string, domain: string): Promise<{ id: number } | ServiceError> =>
withAuth(async (session) => {
const org = await prisma.org.create({
@@ -695,6 +699,38 @@ export const cancelInvite = async (inviteId: string, domain: string): Promise<{
}, /* minRequiredRole = */ OrgRole.OWNER)
);
+export const getMe = async () =>
+ withAuth(async (session) => {
+ const user = await prisma.user.findUnique({
+ where: {
+ id: session.user.id,
+ },
+ include: {
+ orgs: {
+ include: {
+ org: true,
+ }
+ },
+ }
+ });
+
+ if (!user) {
+ return notFound();
+ }
+
+ return {
+ id: user.id,
+ email: user.email,
+ name: user.name,
+ memberships: user.orgs.map((org) => ({
+ id: org.orgId,
+ role: org.role,
+ domain: org.org.domain,
+ name: org.org.name,
+ }))
+ }
+ });
+
export const redeemInvite = async (inviteId: string): Promise<{ success: boolean } | ServiceError> =>
withAuth(async (session) => {
const invite = await prisma.invite.findUnique({
@@ -710,9 +746,9 @@ export const redeemInvite = async (inviteId: string): Promise<{ success: boolean
return notFound();
}
- const user = await getUser(session.user.id);
- if (!user) {
- return notFound();
+ const user = await getMe();
+ if (isServiceError(user)) {
+ return user;
}
// Check if the user is the recipient of the invite
@@ -765,10 +801,10 @@ export const redeemInvite = async (inviteId: string): Promise<{ success: boolean
});
export const getInviteInfo = async (inviteId: string) =>
- withAuth(async (session) => {
- const user = await getUser(session.user.id);
- if (!user) {
- return notFound();
+ withAuth(async () => {
+ const user = await getMe();
+ if (isServiceError(user)) {
+ return user;
}
const invite = await prisma.invite.findUnique({
@@ -880,9 +916,9 @@ export const createOnboardingSubscription = async (domain: string) =>
return notFound();
}
- const user = await getUser(session.user.id);
- if (!user) {
- return notFound();
+ const user = await getMe();
+ if (isServiceError(user)) {
+ return user;
}
if (!stripeClient) {
diff --git a/packages/web/src/app/[domain]/components/orgSelector/index.tsx b/packages/web/src/app/[domain]/components/orgSelector/index.tsx
index e4c89908..769a072e 100644
--- a/packages/web/src/app/[domain]/components/orgSelector/index.tsx
+++ b/packages/web/src/app/[domain]/components/orgSelector/index.tsx
@@ -1,7 +1,7 @@
-import { auth } from "@/auth";
-import { getUserOrgs } from "../../../../data/user";
import { OrgSelectorDropdown } from "./orgSelectorDropdown";
import { prisma } from "@/prisma";
+import { getMe } from "@/actions";
+import { isServiceError } from "@/lib/utils";
interface OrgSelectorProps {
domain: string;
@@ -10,12 +10,11 @@ interface OrgSelectorProps {
export const OrgSelector = async ({
domain,
}: OrgSelectorProps) => {
- const session = await auth();
- if (!session) {
+ const user = await getMe();
+ if (isServiceError(user)) {
return null;
}
- const orgs = await getUserOrgs(session.user.id);
const activeOrg = await prisma.org.findUnique({
where: {
domain,
@@ -28,10 +27,10 @@ export const OrgSelector = async ({
return (
({
- name: org.name,
- id: org.id,
- domain: org.domain,
+ orgs={user.memberships.map(({ name, domain, id }) => ({
+ name,
+ domain,
+ id,
}))}
activeOrgId={activeOrg.id}
/>
diff --git a/packages/web/src/app/[domain]/connections/[id]/page.tsx b/packages/web/src/app/[domain]/connections/[id]/page.tsx
index 5e5c8be3..7aa97735 100644
--- a/packages/web/src/app/[domain]/connections/[id]/page.tsx
+++ b/packages/web/src/app/[domain]/connections/[id]/page.tsx
@@ -15,7 +15,6 @@ import { ConfigSetting } from "./components/configSetting"
import { DeleteConnectionSetting } from "./components/deleteConnectionSetting"
import { DisplayNameSetting } from "./components/displayNameSetting"
import { RepoList } from "./components/repoList"
-import { auth } from "@/auth"
import { getConnectionByDomain } from "@/data/connection"
import { Overview } from "./components/overview"
@@ -30,11 +29,6 @@ interface ConnectionManagementPageProps {
}
export default async function ConnectionManagementPage({ params, searchParams }: ConnectionManagementPageProps) {
- const session = await auth();
- if (!session) {
- return null;
- }
-
const connection = await getConnectionByDomain(Number(params.id), params.domain);
if (!connection) {
return
@@ -42,7 +36,6 @@ export default async function ConnectionManagementPage({ params, searchParams }:
const currentTab = searchParams.tab || "overview";
-
return (
diff --git a/packages/web/src/app/[domain]/repos/repositoryTable.tsx b/packages/web/src/app/[domain]/repos/repositoryTable.tsx
index 59ee672b..14cc6a33 100644
--- a/packages/web/src/app/[domain]/repos/repositoryTable.tsx
+++ b/packages/web/src/app/[domain]/repos/repositoryTable.tsx
@@ -11,7 +11,11 @@ import { useMemo } from "react";
import { Skeleton } from "@/components/ui/skeleton";
import { env } from "@/env.mjs";
-export const RepositoryTable = () => {
+interface RepositoryTableProps {
+ isAddNewRepoButtonVisible: boolean;
+}
+
+export const RepositoryTable = ({ isAddNewRepoButtonVisible }: RepositoryTableProps) => {
const domain = useDomain();
const { data: repos, isLoading: reposLoading, error: reposError } = useQuery({
@@ -48,31 +52,31 @@ export const RepositoryTable = () => {
const tableColumns = useMemo(() => {
if (reposLoading) {
- return columns(domain).map((column) => {
+ return columns(domain, isAddNewRepoButtonVisible).map((column) => {
if ('accessorKey' in column && column.accessorKey === "name") {
- return {
+ return {
+ ...column,
+ cell: () => (
+
+ {/* Avatar skeleton */}
+ {/* Repository name skeleton */}
+
+ ),
+ }
+ }
+
+ return {
...column,
cell: () => (
-
- {/* Avatar skeleton */}
- {/* Repository name skeleton */}
-
+
+
+
),
- }
- }
-
- return {
- ...column,
- cell: () => (
-
-
-
- ),
}
- })
+ })
}
- return columns(domain);
+ return columns(domain, isAddNewRepoButtonVisible);
}, [reposLoading, domain]);
diff --git a/packages/web/src/app/[domain]/upgrade/page.tsx b/packages/web/src/app/[domain]/upgrade/page.tsx
index cd46aaf9..cd8f238b 100644
--- a/packages/web/src/app/[domain]/upgrade/page.tsx
+++ b/packages/web/src/app/[domain]/upgrade/page.tsx
@@ -9,8 +9,13 @@ import { isServiceError } from "@/lib/utils";
import Link from "next/link";
import { ArrowLeftIcon } from "@radix-ui/react-icons";
import { LogoutEscapeHatch } from "@/app/components/logoutEscapeHatch";
+import { env } from "@/env.mjs";
+import { IS_BILLING_ENABLED } from "@/lib/stripe";
export default async function Upgrade({ params: { domain } }: { params: { domain: string } }) {
+ if (!IS_BILLING_ENABLED) {
+ redirect(`/${domain}`);
+ }
const subscription = await fetchSubscription(domain);
if (!subscription) {
@@ -52,9 +57,11 @@ export default async function Upgrade({ params: { domain } }: { params: { domain
-
+ {env.SOURCEBOT_TENANCY_MODE === 'multi' && (
+
+ )}
{
- const user = await prisma.user.findUnique({
- where: { email }
- });
-
- // The user doesn't exist, so create a new one.
- if (!user) {
- const hashedPassword = bcrypt.hashSync(password, 10);
- const newUser = await prisma.user.create({
- data: {
- email,
- hashedPassword,
- }
- });
-
- return {
- id: newUser.id,
- email: newUser.email,
- }
-
- // Otherwise, the user exists, so verify the password.
- } else {
- if (!user.hashedPassword) {
- return null;
- }
-
- if (!bcrypt.compareSync(password, user.hashedPassword)) {
- return null;
- }
-
- return {
- id: user.id,
- email: user.email,
- name: user.name ?? undefined,
- image: user.image ?? undefined,
- };
- }
-
-}
\ No newline at end of file
diff --git a/packages/web/src/app/api/(server)/repos/route.ts b/packages/web/src/app/api/(server)/repos/route.ts
index ba13f3e6..bab9bc9d 100644
--- a/packages/web/src/app/api/(server)/repos/route.ts
+++ b/packages/web/src/app/api/(server)/repos/route.ts
@@ -22,5 +22,5 @@ const getRepos = (domain: string) =>
withOrgMembership(session, domain, async ({ orgId }) => {
const response = await listRepositories(orgId);
return response;
- })
- );
\ No newline at end of file
+ }
+ ), /* allowSingleTenantUnauthedAccess */ true);
\ No newline at end of file
diff --git a/packages/web/src/app/api/(server)/search/route.ts b/packages/web/src/app/api/(server)/search/route.ts
index 4bb119a9..813640e7 100644
--- a/packages/web/src/app/api/(server)/search/route.ts
+++ b/packages/web/src/app/api/(server)/search/route.ts
@@ -30,4 +30,5 @@ const postSearch = (request: SearchRequest, domain: string) =>
withOrgMembership(session, domain, async ({ orgId }) => {
const response = await search(request, orgId);
return response;
- }))
\ No newline at end of file
+ }
+ ), /* allowSingleTenantUnauthedAccess */ true);
\ No newline at end of file
diff --git a/packages/web/src/app/api/(server)/source/route.ts b/packages/web/src/app/api/(server)/source/route.ts
index 858857f6..1c9318b4 100644
--- a/packages/web/src/app/api/(server)/source/route.ts
+++ b/packages/web/src/app/api/(server)/source/route.ts
@@ -32,4 +32,5 @@ const postSource = (request: FileSourceRequest, domain: string) =>
withOrgMembership(session, domain, async ({ orgId }) => {
const response = await getFileSource(request, orgId);
return response;
- }));
+ }
+ ), /* allowSingleTenantUnauthedAccess */ true);
diff --git a/packages/web/src/auth.ts b/packages/web/src/auth.ts
index bcac55b3..39b76523 100644
--- a/packages/web/src/auth.ts
+++ b/packages/web/src/auth.ts
@@ -1,5 +1,5 @@
import 'next-auth/jwt';
-import NextAuth, { DefaultSession } from "next-auth"
+import NextAuth, { DefaultSession, User as AuthJsUser } from "next-auth"
import GitHub from "next-auth/providers/github"
import Google from "next-auth/providers/google"
import Credentials from "next-auth/providers/credentials"
@@ -7,13 +7,15 @@ import EmailProvider from "next-auth/providers/nodemailer";
import { PrismaAdapter } from "@auth/prisma-adapter"
import { prisma } from "@/prisma";
import { env } from "@/env.mjs";
-import { User } from '@sourcebot/db';
+import { OrgRole, User } from '@sourcebot/db';
import 'next-auth/jwt';
import type { Provider } from "next-auth/providers";
-import { verifyCredentialsRequestSchema, verifyCredentialsResponseSchema } from './lib/schemas';
+import { verifyCredentialsRequestSchema } from './lib/schemas';
import { createTransport } from 'nodemailer';
import { render } from '@react-email/render';
import MagicLinkEmail from './emails/magicLinkEmail';
+import { SINGLE_TENANT_ORG_ID } from './lib/constants';
+import bcrypt from 'bcrypt';
export const runtime = 'nodejs';
@@ -89,24 +91,45 @@ export const getProviders = () => {
return null;
}
const { email, password } = body.data;
-
- // 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', env.AUTH_URL), {
- method: 'POST',
- body: JSON.stringify({ email, password }),
+
+ const user = await prisma.user.findUnique({
+ where: { email }
});
-
- if (!response.ok) {
- return null;
- }
-
- const user = verifyCredentialsResponseSchema.parse(await response.json());
- return {
- id: user.id,
- email: user.email,
- name: user.name,
- image: user.image,
+
+ // The user doesn't exist, so create a new one.
+ if (!user) {
+ const hashedPassword = bcrypt.hashSync(password, 10);
+ const newUser = await prisma.user.create({
+ data: {
+ email,
+ hashedPassword,
+ }
+ });
+
+ const authJsUser: AuthJsUser = {
+ id: newUser.id,
+ email: newUser.email,
+ }
+
+ onCreateUser({ user: authJsUser });
+ return authJsUser;
+
+ // Otherwise, the user exists, so verify the password.
+ } else {
+ if (!user.hashedPassword) {
+ return null;
+ }
+
+ if (!bcrypt.compareSync(password, user.hashedPassword)) {
+ return null;
+ }
+
+ return {
+ id: user.id,
+ email: user.email,
+ name: user.name ?? undefined,
+ image: user.image ?? undefined,
+ };
}
}
}));
@@ -115,6 +138,47 @@ export const getProviders = () => {
return providers;
}
+const onCreateUser = async ({ user }: { user: AuthJsUser }) => {
+ // In single-tenant mode w/ auth, we assign the first user to sign
+ // up as the owner of the default org.
+ if (
+ env.SOURCEBOT_TENANCY_MODE === 'single' &&
+ env.SOURCEBOT_AUTH_ENABLED === 'true'
+ ) {
+ await prisma.$transaction(async (tx) => {
+ const defaultOrg = await tx.org.findUnique({
+ where: {
+ id: SINGLE_TENANT_ORG_ID,
+ },
+ include: {
+ members: true,
+ }
+ });
+
+ // Only the first user to sign up will be an owner of the default org.
+ if (defaultOrg?.members.length === 0) {
+ await tx.org.update({
+ where: {
+ id: SINGLE_TENANT_ORG_ID,
+ },
+ data: {
+ members: {
+ create: {
+ role: OrgRole.OWNER,
+ user: {
+ connect: {
+ id: user.id,
+ }
+ }
+ }
+ }
+ }
+ });
+ }
+ });
+ }
+}
+
const useSecureCookies = env.AUTH_URL?.startsWith("https://") ?? false;
const hostName = env.AUTH_URL ? new URL(env.AUTH_URL).hostname : "localhost";
@@ -125,6 +189,9 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
strategy: "jwt",
},
trustHost: true,
+ events: {
+ createUser: onCreateUser,
+ },
callbacks: {
async jwt({ token, user: _user }) {
const user = _user as User | undefined;
diff --git a/packages/web/src/env.mjs b/packages/web/src/env.mjs
index b93e7a2e..f77fd7ed 100644
--- a/packages/web/src/env.mjs
+++ b/packages/web/src/env.mjs
@@ -44,6 +44,7 @@ export const env = createEnv({
DATABASE_URL: z.string().url(),
SOURCEBOT_TENANCY_MODE: tenancyModeSchema.default("multi"),
+ SOURCEBOT_AUTH_ENABLED: booleanSchema.default('true'),
},
// @NOTE: Make sure you destructure all client variables in the
// `experimental__runtimeEnv` block below.
diff --git a/packages/web/src/initialize.ts b/packages/web/src/initialize.ts
index 510ed8a7..6ef83e49 100644
--- a/packages/web/src/initialize.ts
+++ b/packages/web/src/initialize.ts
@@ -3,18 +3,11 @@ import { env } from './env.mjs';
import { prisma } from "@/prisma";
import { SINGLE_TENANT_USER_ID, SINGLE_TENANT_ORG_ID, SINGLE_TENANT_ORG_DOMAIN, SINGLE_TENANT_ORG_NAME, SINGLE_TENANT_USER_EMAIL } from './lib/constants';
-const initSingleTenancy = async () => {
- const user = await prisma.user.upsert({
- where: {
- id: SINGLE_TENANT_USER_ID,
- },
- update: {},
- create: {
- id: SINGLE_TENANT_USER_ID,
- email: SINGLE_TENANT_USER_EMAIL,
- },
- });
+if (env.SOURCEBOT_AUTH_ENABLED === 'false' && env.SOURCEBOT_TENANCY_MODE === 'multi') {
+ throw new Error('SOURCEBOT_AUTH_ENABLED must be true when SOURCEBOT_TENANCY_MODE is multi');
+}
+const initSingleTenancy = async () => {
await prisma.org.upsert({
where: {
id: SINGLE_TENANT_ORG_ID,
@@ -24,21 +17,39 @@ const initSingleTenancy = async () => {
name: SINGLE_TENANT_ORG_NAME,
domain: SINGLE_TENANT_ORG_DOMAIN,
id: SINGLE_TENANT_ORG_ID,
- isOnboarded: true,
- members: {
- create: {
- role: OrgRole.OWNER,
- user: {
- connect: {
- id: user.id,
+ isOnboarded: env.SOURCEBOT_AUTH_ENABLED === 'false',
+ }
+ });
+
+ if (env.SOURCEBOT_AUTH_ENABLED === 'false') {
+ // Default user for single tenancy unauthed access
+ await prisma.user.upsert({
+ where: {
+ id: SINGLE_TENANT_USER_ID,
+ },
+ update: {},
+ create: {
+ id: SINGLE_TENANT_USER_ID,
+ email: SINGLE_TENANT_USER_EMAIL,
+ },
+ });
+
+ await prisma.org.update({
+ where: {
+ id: SINGLE_TENANT_ORG_ID,
+ },
+ data: {
+ members: {
+ create: {
+ role: OrgRole.MEMBER,
+ user: {
+ connect: { id: SINGLE_TENANT_USER_ID }
}
}
}
}
- }
- });
-
- console.log('init!');
+ });
+ }
}
if (env.SOURCEBOT_TENANCY_MODE === 'single') {
diff --git a/packages/web/src/lib/constants.ts b/packages/web/src/lib/constants.ts
index 0a0fec79..faee5be9 100644
--- a/packages/web/src/lib/constants.ts
+++ b/packages/web/src/lib/constants.ts
@@ -25,7 +25,7 @@ export const TEAM_FEATURES = [
export const MOBILE_UNSUPPORTED_SPLASH_SCREEN_DISMISSED_COOKIE_NAME = 'sb.mobile-unsupported-splash-screen-dismissed';
export const SINGLE_TENANT_USER_ID = '1';
-export const SINGLE_TENANT_USER_EMAIL = 'default@example.com';
+export const SINGLE_TENANT_USER_EMAIL = 'default@sourcebot.dev';
export const SINGLE_TENANT_ORG_ID = 1;
export const SINGLE_TENANT_ORG_DOMAIN = '~';
export const SINGLE_TENANT_ORG_NAME = 'default';
\ No newline at end of file
diff --git a/packages/web/src/lib/schemas.ts b/packages/web/src/lib/schemas.ts
index 0345aac7..93c52345 100644
--- a/packages/web/src/lib/schemas.ts
+++ b/packages/web/src/lib/schemas.ts
@@ -183,14 +183,6 @@ export const verifyCredentialsRequestSchema = z.object({
password: z.string().min(8),
});
-
-export const verifyCredentialsResponseSchema = z.object({
- id: z.string().optional(),
- name: z.string().optional(),
- email: z.string().optional(),
- image: z.string().optional(),
-});
-
export const orgNameSchema = z.string().min(2, { message: "Organization name must be at least 3 characters long." });
export const orgDomainSchema = z.string()
diff --git a/packages/web/src/middleware.ts b/packages/web/src/middleware.ts
index 825be57b..b8c1580c 100644
--- a/packages/web/src/middleware.ts
+++ b/packages/web/src/middleware.ts
@@ -3,14 +3,23 @@ import type { NextRequest } from 'next/server'
import { env } from './env.mjs'
import { SINGLE_TENANT_ORG_DOMAIN } from '@/lib/constants'
-// The following middleware is used to redirect all requests to
-// the single tenant domain (when in single tenant mode).
export async function middleware(request: NextRequest) {
+ const url = request.nextUrl.clone();
+
if (env.SOURCEBOT_TENANCY_MODE !== 'single') {
return NextResponse.next();
}
- const url = request.nextUrl.clone();
+ // Enable these domains when auth is enabled.
+ if (env.SOURCEBOT_AUTH_ENABLED === 'true' &&
+ (
+ url.pathname.startsWith('/login') ||
+ url.pathname.startsWith('/redeem')
+ )
+ ) {
+ return NextResponse.next();
+ }
+
const pathSegments = url.pathname.split('/').filter(Boolean);
const currentDomain = pathSegments[0];
From 85166af175077a01cd4d1acfcecce70c116da9ad Mon Sep 17 00:00:00 2001
From: bkellam
Date: Thu, 20 Mar 2025 10:03:14 -0700
Subject: [PATCH 5/6] random: set CONFIG_MAX_REPOS_NO_TOKEN default to max int,
effectively disabling this feature by default (since this is only useful in
certain scenarios like cloud)
---
packages/web/src/actions.ts | 18 +++++++++---------
packages/web/src/env.mjs | 2 +-
2 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/packages/web/src/actions.ts b/packages/web/src/actions.ts
index 1c6bf587..b82ef598 100644
--- a/packages/web/src/actions.ts
+++ b/packages/web/src/actions.ts
@@ -1433,6 +1433,15 @@ const parseConnectionConfig = (connectionType: string, config: string) => {
} satisfies ServiceError;
}
+ const isValidConfig = ajv.validate(schema, parsedConfig);
+ if (!isValidConfig) {
+ return {
+ statusCode: StatusCodes.BAD_REQUEST,
+ errorCode: ErrorCode.INVALID_REQUEST_BODY,
+ message: `config schema validation failed with errors: ${ajv.errorsText(ajv.errors)}`,
+ } satisfies ServiceError;
+ }
+
const { numRepos, hasToken } = (() => {
switch (connectionType) {
case "github": {
@@ -1479,15 +1488,6 @@ const parseConnectionConfig = (connectionType: string, config: string) => {
} satisfies ServiceError;
}
- const isValidConfig = ajv.validate(schema, parsedConfig);
- if (!isValidConfig) {
- return {
- statusCode: StatusCodes.BAD_REQUEST,
- errorCode: ErrorCode.INVALID_REQUEST_BODY,
- message: `config schema validation failed with errors: ${ajv.errorsText(ajv.errors)}`,
- } satisfies ServiceError;
- }
-
return parsedConfig;
}
diff --git a/packages/web/src/env.mjs b/packages/web/src/env.mjs
index f77fd7ed..2b5f1df0 100644
--- a/packages/web/src/env.mjs
+++ b/packages/web/src/env.mjs
@@ -37,7 +37,7 @@ export const env = createEnv({
STRIPE_ENABLE_TEST_CLOCKS: booleanSchema.default('false'),
// Misc
- CONFIG_MAX_REPOS_NO_TOKEN: numberSchema.default(500),
+ CONFIG_MAX_REPOS_NO_TOKEN: numberSchema.default(Number.MAX_SAFE_INTEGER),
SOURCEBOT_ROOT_DOMAIN: z.string().default("localhost:3000"),
NODE_ENV: z.enum(["development", "test", "production"]),
SOURCEBOT_TELEMETRY_DISABLED: booleanSchema.default('false'),
From ed945d2b0463e31b84c5c49875b67decf7f70c17 Mon Sep 17 00:00:00 2001
From: bkellam
Date: Thu, 20 Mar 2025 10:04:12 -0700
Subject: [PATCH 6/6] Change the tenancy mode to default to single
---
packages/web/src/env.mjs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/web/src/env.mjs b/packages/web/src/env.mjs
index 2b5f1df0..612b77f5 100644
--- a/packages/web/src/env.mjs
+++ b/packages/web/src/env.mjs
@@ -43,7 +43,7 @@ export const env = createEnv({
SOURCEBOT_TELEMETRY_DISABLED: booleanSchema.default('false'),
DATABASE_URL: z.string().url(),
- SOURCEBOT_TENANCY_MODE: tenancyModeSchema.default("multi"),
+ SOURCEBOT_TENANCY_MODE: tenancyModeSchema.default("single"),
SOURCEBOT_AUTH_ENABLED: booleanSchema.default('true'),
},
// @NOTE: Make sure you destructure all client variables in the