move feature to EE

This commit is contained in:
bkellam 2025-09-19 18:05:18 -07:00
parent c7e2f5ae06
commit 0e527f4e08
10 changed files with 44 additions and 22 deletions

View file

@ -2,7 +2,7 @@ Copyright (c) 2025 Taqla Inc.
Portions of this software are licensed as follows:
- All content that resides under the "ee/", "packages/web/src/ee/", and "packages/shared/src/ee/" directories of this repository, if these directories exist, is licensed under the license defined in "ee/LICENSE".
- All content that resides under the "ee/", "packages/web/src/ee/", "packages/backend/src/ee/", and "packages/shared/src/ee/" directories of this repository, if these directories exist, is licensed under the license defined in "ee/LICENSE".
- All third party components incorporated into the Sourcebot Software are licensed under the original license provided by the owner of the applicable component.
- Content outside of the above mentioned directories or restrictions above is available under the "Functional Source License" as defined below.

View file

@ -7,10 +7,11 @@ import { GithubConnectionConfig } from "@sourcebot/schemas/v3/github.type";
import { GitlabConnectionConfig } from "@sourcebot/schemas/v3/gitlab.type";
import { Job, Queue, Worker } from 'bullmq';
import { Redis } from 'ioredis';
import { env } from "./env.js";
import { createOctokitFromConfig, getUserIdsWithReadAccessToRepo } from "./github.js";
import { RepoWithConnections } from "./types.js";
import { PERMISSION_SYNC_SUPPORTED_CODE_HOST_TYPES } from "./constants.js";
import { env } from "../env.js";
import { createOctokitFromConfig, getUserIdsWithReadAccessToRepo } from "../github.js";
import { RepoWithConnections } from "../types.js";
import { PERMISSION_SYNC_SUPPORTED_CODE_HOST_TYPES } from "../constants.js";
import { hasEntitlement } from "@sourcebot/shared";
type RepoPermissionSyncJob = {
jobId: string;
@ -41,6 +42,10 @@ export class RepoPermissionSyncer {
}
public startScheduler() {
if (!hasEntitlement('permission-syncing')) {
throw new Error('Permission syncing is not supported in current plan.');
}
logger.debug('Starting scheduler');
return setInterval(async () => {

View file

@ -4,9 +4,10 @@ import { PrismaClient, User, UserPermissionSyncJobStatus } from "@sourcebot/db";
import { createLogger } from "@sourcebot/logger";
import { Job, Queue, Worker } from "bullmq";
import { Redis } from "ioredis";
import { PERMISSION_SYNC_SUPPORTED_CODE_HOST_TYPES } from "./constants.js";
import { env } from "./env.js";
import { getReposThatAuthenticatedUserHasReadAccessTo } from "./github.js";
import { PERMISSION_SYNC_SUPPORTED_CODE_HOST_TYPES } from "../constants.js";
import { env } from "../env.js";
import { getReposThatAuthenticatedUserHasReadAccessTo } from "../github.js";
import { hasEntitlement } from "@sourcebot/shared";
const logger = createLogger('user-permission-syncer');
@ -37,6 +38,10 @@ export class UserPermissionSyncer {
}
public startScheduler() {
if (!hasEntitlement('permission-syncing')) {
throw new Error('Permission syncing is not supported in current plan.');
}
logger.debug('Starting scheduler');
return setInterval(async () => {

View file

@ -53,7 +53,7 @@ export const env = createEnv({
GITLAB_CLIENT_QUERY_TIMEOUT_SECONDS: numberSchema.default(60 * 10),
EXPERIMENT_PERMISSION_SYNC_ENABLED: booleanSchema.default("false"),
EXPERIMENT_EE_PERMISSION_SYNC_ENABLED: booleanSchema.default('false'),
},
runtimeEnv: process.env,
emptyStringAsUndefined: true,

View file

@ -2,7 +2,7 @@ import "./instrument.js";
import { PrismaClient } from "@sourcebot/db";
import { createLogger } from "@sourcebot/logger";
import { loadConfig } from '@sourcebot/shared';
import { hasEntitlement, loadConfig } from '@sourcebot/shared';
import { existsSync } from 'fs';
import { mkdir } from 'fs/promises';
import { Redis } from 'ioredis';
@ -10,11 +10,11 @@ import path from 'path';
import { ConnectionManager } from './connectionManager.js';
import { DEFAULT_SETTINGS } from './constants.js';
import { env } from "./env.js";
import { RepoPermissionSyncer } from './repoPermissionSyncer.js';
import { RepoPermissionSyncer } from './ee/repoPermissionSyncer.js';
import { PromClient } from './promClient.js';
import { RepoManager } from './repoManager.js';
import { AppContext } from "./types.js";
import { UserPermissionSyncer } from "./userPermissionSyncer.js";
import { UserPermissionSyncer } from "./ee/userPermissionSyncer.js";
const logger = createLogger('backend-entrypoint');
@ -76,9 +76,18 @@ await repoManager.validateIndexedReposHaveShards();
const connectionManagerInterval = connectionManager.startScheduler();
const repoManagerInterval = repoManager.startScheduler();
const repoPermissionSyncerInterval = env.EXPERIMENT_PERMISSION_SYNC_ENABLED === 'true' ? repoPermissionSyncer.startScheduler() : null;
const userPermissionSyncerInterval = env.EXPERIMENT_PERMISSION_SYNC_ENABLED === 'true' ? userPermissionSyncer.startScheduler() : null;
let repoPermissionSyncerInterval: NodeJS.Timeout | null = null;
let userPermissionSyncerInterval: NodeJS.Timeout | null = null;
if (env.EXPERIMENT_EE_PERMISSION_SYNC_ENABLED === 'true' && !hasEntitlement('permission-syncing')) {
logger.error('Permission syncing is not supported in current plan. Please contact support@sourcebot.dev for assistance.');
process.exit(1);
}
else if (env.EXPERIMENT_EE_PERMISSION_SYNC_ENABLED === 'true' && hasEntitlement('permission-syncing')) {
repoPermissionSyncerInterval = repoPermissionSyncer.startScheduler();
userPermissionSyncerInterval = userPermissionSyncer.startScheduler();
}
const cleanup = async (signal: string) => {
logger.info(`Recieved ${signal}, cleaning up...`);

View file

@ -38,15 +38,16 @@ const entitlements = [
"sso",
"code-nav",
"audit",
"analytics"
"analytics",
"permission-syncing"
] as const;
export type Entitlement = (typeof entitlements)[number];
const entitlementsByPlan: Record<Plan, Entitlement[]> = {
oss: ["anonymous-access"],
"cloud:team": ["billing", "multi-tenancy", "sso", "code-nav"],
"self-hosted:enterprise": ["search-contexts", "sso", "code-nav", "audit", "analytics"],
"self-hosted:enterprise-unlimited": ["search-contexts", "anonymous-access", "sso", "code-nav", "audit", "analytics"],
"self-hosted:enterprise": ["search-contexts", "sso", "code-nav", "audit", "analytics", "permission-syncing"],
"self-hosted:enterprise-unlimited": ["search-contexts", "anonymous-access", "sso", "code-nav", "audit", "analytics", "permission-syncing"],
// Special entitlement for https://demo.sourcebot.dev
"cloud:demo": ["anonymous-access", "code-nav", "search-contexts"],
} as const;

View file

@ -1035,7 +1035,7 @@ export const flagReposForIndex = async (repoIds: number[], domain: string) => se
where: {
id: { in: repoIds },
orgId: org.id,
...(env.EXPERIMENT_PERMISSION_SYNC_ENABLED === 'true' ? {
...(env.EXPERIMENT_EE_PERMISSION_SYNC_ENABLED === 'true' ? {
permittedUsers: {
some: {
userId: userId,

View file

@ -12,6 +12,7 @@ import Credentials from "next-auth/providers/credentials";
import type { User as AuthJsUser } from "next-auth";
import { onCreateUser } from "@/lib/authUtils";
import { createLogger } from "@sourcebot/logger";
import { hasEntitlement } from "@sourcebot/shared";
const logger = createLogger('web-sso');
@ -30,10 +31,10 @@ export const getSSOProviders = (): Provider[] => {
scope: [
'read:user',
'user:email',
// Permission syncing requires the `repo` in order to fetch repositories
// Permission syncing requires the `repo` scope in order to fetch repositories
// for the authenticated user.
// @see: https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#list-repositories-for-the-authenticated-user
...(env.EXPERIMENT_PERMISSION_SYNC_ENABLED === 'true' ?
...(env.EXPERIMENT_EE_PERMISSION_SYNC_ENABLED === 'true' && hasEntitlement('permission-syncing') ?
['repo'] :
[]
),

View file

@ -137,7 +137,7 @@ export const env = createEnv({
// @NOTE: Take care to update actions.ts when changing the name of this.
EXPERIMENT_SELF_SERVE_REPO_INDEXING_GITHUB_TOKEN: z.string().optional(),
EXPERIMENT_PERMISSION_SYNC_ENABLED: booleanSchema.default('false'),
EXPERIMENT_EE_PERMISSION_SYNC_ENABLED: booleanSchema.default('false'),
},
// @NOTE: Please make sure of the following:
// - Make sure you destructure all client variables in

View file

@ -1,6 +1,7 @@
import 'server-only';
import { env } from "@/env.mjs";
import { Prisma, PrismaClient } from "@sourcebot/db";
import { hasEntitlement } from "@sourcebot/shared";
// @see: https://authjs.dev/getting-started/adapters/prisma
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient }
@ -24,7 +25,7 @@ export const userScopedPrismaClientExtension = (userId?: string) => {
(prisma) => {
return prisma.$extends({
query: {
...(env.EXPERIMENT_PERMISSION_SYNC_ENABLED === 'true' ? {
...(env.EXPERIMENT_EE_PERMISSION_SYNC_ENABLED === 'true' && hasEntitlement('permission-syncing') ? {
repo: {
$allOperations({ args, query }) {
if ('where' in args) {