diff --git a/.env.development b/.env.development index 0eca799ff..39b25125c 100644 --- a/.env.development +++ b/.env.development @@ -69,6 +69,5 @@ SOURCEBOT_TELEMETRY_DISABLED=true # Disables telemetry collection # CONFIG_MAX_REPOS_NO_TOKEN= NODE_ENV=development -# SOURCEBOT_TENANCY_MODE=single DEBUG_WRITE_CHAT_MESSAGES_TO_FILE=true diff --git a/docs/docs/configuration/tenancy.mdx b/docs/docs/configuration/tenancy.mdx deleted file mode 100644 index b70412a9c..000000000 --- a/docs/docs/configuration/tenancy.mdx +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Multi Tenancy Mode -sidebarTitle: Multi tenancy ---- - -If you're switching from single-tenant mode, delete the Sourcebot cache (the `.sourcebot` folder) before starting. -[Authentication](/docs/configuration/auth/overview) must be enabled to enable multi tenancy mode -Multi tenancy allows your Sourcebot deployment to have **multiple organizations**, each with their own set of members and repos. To enable multi tenancy mode, define an environment variable -named `SOURCEBOT_TENANCY_MODE` and set its value to `multi`. When multi tenancy mode is enabled: - -- Any members or repos that are configured in an organization are isolated to that organization -- Members must be invited to an organization to gain access -- Members may be a part of multiple organizations and switch through them in the UI - - -### Organization creation form - -When you sign in for the first time (assuming you didn't go through an invite), you'll be presented with the organization creation form. The member who creates -the organization will be the Owner. - -![Org creation](/images/org_create.png) - -### Switching between organizations - -To switch between organizations, press the drop down on the top left of the navigation menu. This also provides an option to create a new organization: - -![Org switching](/images/org_switch.png) diff --git a/packages/shared/src/env.server.ts b/packages/shared/src/env.server.ts index 3b852cac1..62b447638 100644 --- a/packages/shared/src/env.server.ts +++ b/packages/shared/src/env.server.ts @@ -6,7 +6,6 @@ import { readFile } from 'fs/promises'; import stripJsonComments from "strip-json-comments"; import { z } from "zod"; import { getTokenFromConfig } from "./crypto.js"; -import { tenancyModeSchema } from "./types.js"; // Booleans are specified as 'true' or 'false' strings. const booleanSchema = z.enum(["true", "false"]); @@ -182,7 +181,6 @@ const options = { DATABASE_NAME: z.string().optional(), DATABASE_ARGS: z.string().optional(), - SOURCEBOT_TENANCY_MODE: tenancyModeSchema.default("single"), CONFIG_PATH: z.string(), // Misc UI flags diff --git a/packages/shared/src/index.server.ts b/packages/shared/src/index.server.ts index 9f8dc3c27..a1eb34204 100644 --- a/packages/shared/src/index.server.ts +++ b/packages/shared/src/index.server.ts @@ -17,7 +17,6 @@ export type { export { repoMetadataSchema, repoIndexingJobMetadataSchema, - tenancyModeSchema, } from "./types.js"; export { base64Decode, diff --git a/packages/shared/src/types.ts b/packages/shared/src/types.ts index d52a09923..b0291a57b 100644 --- a/packages/shared/src/types.ts +++ b/packages/shared/src/types.ts @@ -64,6 +64,4 @@ export const repoIndexingJobMetadataSchema = z.object({ export type RepoIndexingJobMetadata = z.infer; -export const tenancyModeSchema = z.enum(["multi", "single"]); - export type IdentityProviderType = IdentityProviderConfig['provider']; \ No newline at end of file diff --git a/packages/web/src/app/[domain]/components/navigationMenu/index.tsx b/packages/web/src/app/[domain]/components/navigationMenu/index.tsx index 49b641f5b..b47afb67f 100644 --- a/packages/web/src/app/[domain]/components/navigationMenu/index.tsx +++ b/packages/web/src/app/[domain]/components/navigationMenu/index.tsx @@ -4,12 +4,10 @@ import { auth } from "@/auth"; import { Button } from "@/components/ui/button"; import { NavigationMenu as NavigationMenuBase } from "@/components/ui/navigation-menu"; import { Separator } from "@/components/ui/separator"; -import { env } from "@sourcebot/shared"; import { ServiceErrorException } from "@/lib/serviceError"; import { isServiceError } from "@/lib/utils"; import { OrgRole, RepoIndexingJobStatus, RepoIndexingJobType } from "@sourcebot/db"; import Link from "next/link"; -import { OrgSelector } from "../orgSelector"; import { MeControlDropdownMenu } from "../meControlDropdownMenu"; import WhatsNewIndicator from "../whatsNewIndicator"; import { NavigationItems } from "./navigationItems"; @@ -100,15 +98,6 @@ export const NavigationMenu = async ({ /> - {env.SOURCEBOT_TENANCY_MODE === 'multi' && ( - <> - - - - )} - { - const user = await getMe(); - if (isServiceError(user)) { - return null; - } - - const activeOrg = await prisma.org.findUnique({ - where: { - domain, - } - }); - - if (!activeOrg) { - return null; - } - - return ( - ({ - name, - domain, - id, - }))} - activeOrgId={activeOrg.id} - /> - ) -} \ No newline at end of file diff --git a/packages/web/src/app/[domain]/components/orgSelector/orgIcon.tsx b/packages/web/src/app/[domain]/components/orgSelector/orgIcon.tsx deleted file mode 100644 index 2be5bedc8..000000000 --- a/packages/web/src/app/[domain]/components/orgSelector/orgIcon.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { cn } from "@/lib/utils"; -import placeholderAvatar from "@/public/placeholder_avatar.png"; -import { cva } from "class-variance-authority"; -import Image from "next/image"; - -interface OrgIconProps { - className?: string; - size?: "default"; -} - -const iconVariants = cva( - "rounded-full", - { - variants: { - size: { - default: "w-5 h-5" - } - }, - defaultVariants: { - size: "default" - } - }, -) - -export const OrgIcon = ({ - className, - size, -}: OrgIconProps) => { - return ( - Organization avatar - ) -} \ No newline at end of file diff --git a/packages/web/src/app/[domain]/components/orgSelector/orgSelectorDropdown.tsx b/packages/web/src/app/[domain]/components/orgSelector/orgSelectorDropdown.tsx deleted file mode 100644 index 42c1ff150..000000000 --- a/packages/web/src/app/[domain]/components/orgSelector/orgSelectorDropdown.tsx +++ /dev/null @@ -1,129 +0,0 @@ -'use client'; - -import { useToast } from "@/components/hooks/use-toast"; -import { Button } from "@/components/ui/button"; -import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"; -import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; -import { CaretSortIcon, CheckIcon, PlusCircledIcon } from "@radix-ui/react-icons"; -import { useRouter } from "next/navigation"; -import { useCallback, useMemo, useState } from "react"; -import { OrgIcon } from "./orgIcon"; - - -interface OrgSelectorDropdownProps { - orgs: { - name: string, - domain: string, - id: number, - }[], - activeOrgId: number, -} - -export const OrgSelectorDropdown = ({ - orgs: _orgs, - activeOrgId -}: OrgSelectorDropdownProps) => { - const [searchFilter, setSearchFilter] = useState(""); - const [isDropdownOpen, setIsDropdownOpen] = useState(false); - const { toast } = useToast(); - const router = useRouter(); - - const activeOrg = _orgs.find((org) => org.id === activeOrgId)!; - const orgs = useMemo(() => { - // always place the active org at the top - return [ - activeOrg, - ..._orgs.filter(org => org.id !== activeOrgId), - ]; - }, [_orgs, activeOrg, activeOrgId]); - - const onSwitchOrg = useCallback((domain: string, orgName: string) => { - router.push(`/${domain}`); - toast({ - description: `✅ Switched to ${orgName}`, - }); - }, [router, toast]); - - return ( - /* - We need to set `modal=false` to fix a issue with having a dialog menu inside of - a dropdown menu. - @see : https://github.com/radix-ui/primitives/issues/1836#issuecomment-1547607143 - */ - - - - - - - - setSearchFilter(value)} - autoFocus={true} - /> - - -

No organization found

-

{`Your search term "${searchFilter}" did not match any organizations.`}

- -
- - {orgs.map((org, index) => ( - onSwitchOrg(org.domain, org.name)} - > -
- - {org.name} -
- {org.id === activeOrgId && ( - - )} -
- ))} -
-
-
-
- {searchFilter.length === 0 && ( - - - - - )} -
-
- ); -} diff --git a/packages/web/src/initialize.ts b/packages/web/src/initialize.ts index 06647e57d..3f1f2bc2a 100644 --- a/packages/web/src/initialize.ts +++ b/packages/web/src/initialize.ts @@ -1,5 +1,4 @@ import { createGuestUser } from '@/lib/authUtils'; -import { SOURCEBOT_SUPPORT_EMAIL } from "@/lib/constants"; import { prisma } from "@/prisma"; import { OrgRole } from '@sourcebot/db'; import { createLogger, env, hasEntitlement, loadConfig } from "@sourcebot/shared"; @@ -35,7 +34,7 @@ const pruneOldGuestUser = async () => { } } -const initSingleTenancy = async () => { +const init = async () => { // This is needed because v4 introduces the GUEST org role as well as making authentication required. // To keep things simple, we'll just delete the old guest user if it exists in the DB await pruneOldGuestUser(); @@ -66,7 +65,7 @@ const initSingleTenancy = async () => { // search contexts that may be present in the DB. This could happen if a deployment had // the entitlement, synced search contexts, and then no longer had the entitlement const hasSearchContextEntitlement = hasEntitlement("search-contexts") - if(!hasSearchContextEntitlement) { + if (!hasSearchContextEntitlement) { await prisma.searchContext.deleteMany({ where: { orgId: SINGLE_TENANT_ORG_ID, @@ -115,20 +114,6 @@ const initSingleTenancy = async () => { } } -const initMultiTenancy = async () => { - const hasMultiTenancyEntitlement = hasEntitlement("multi-tenancy"); - if (!hasMultiTenancyEntitlement) { - logger.error(`SOURCEBOT_TENANCY_MODE is set to ${env.SOURCEBOT_TENANCY_MODE} but your license doesn't have multi-tenancy entitlement. Please contact ${SOURCEBOT_SUPPORT_EMAIL} to request a license upgrade.`); - process.exit(1); - } -} - (async () => { - if (env.SOURCEBOT_TENANCY_MODE === 'single') { - await initSingleTenancy(); - } else if (env.SOURCEBOT_TENANCY_MODE === 'multi') { - await initMultiTenancy(); - } else { - throw new Error(`Invalid SOURCEBOT_TENANCY_MODE: ${env.SOURCEBOT_TENANCY_MODE}`); - } + await init(); })(); diff --git a/packages/web/src/lib/types.ts b/packages/web/src/lib/types.ts index 20c58529f..3d1933437 100644 --- a/packages/web/src/lib/types.ts +++ b/packages/web/src/lib/types.ts @@ -1,6 +1,5 @@ import { z } from "zod"; import { listReposResponseSchema, getVersionResponseSchema, repositoryQuerySchema, searchContextQuerySchema, listReposQueryParamsSchema } from "./schemas"; -import { tenancyModeSchema } from "@sourcebot/shared"; export type KeymapType = "default" | "vim"; @@ -26,7 +25,6 @@ export type NewsItem = { read?: boolean; } -export type TenancyMode = z.infer; export type RepositoryQuery = z.infer; export type SearchContextQuery = z.infer; export type ListReposResponse = z.infer;