import 'server-only'; import { env, getDBConnectionString } from "@sourcebot/shared"; import { Prisma, PrismaClient, UserWithAccounts } from "@sourcebot/db"; import { hasEntitlement } from "@sourcebot/shared"; // @see: https://authjs.dev/getting-started/adapters/prisma const globalForPrisma = globalThis as unknown as { prisma: PrismaClient } const dbConnectionString = getDBConnectionString(); // @NOTE: In almost all cases, the userScopedPrismaClientExtension should be used // (since actions & queries are scoped to a particular user). There are some exceptions // (e.g., in initialize.ts). // // @todo: we can mark this as `__unsafePrisma` in the future once we've migrated // all of the actions & queries to use the userScopedPrismaClientExtension to avoid // accidental misuse. export const prisma = globalForPrisma.prisma || new PrismaClient({ // @note: this code is evaluated at build time, and will throw exceptions if these env vars are not set. // Here we explicitly check if the DATABASE_URL or the individual database variables are set, and only ...(dbConnectionString !== undefined ? { datasources: { db: { url: dbConnectionString, }, } } : {}), }) if (env.NODE_ENV !== "production") globalForPrisma.prisma = prisma /** * Creates a prisma client extension that scopes queries to striclty information * a given user should be able to access. */ export const userScopedPrismaClientExtension = (user?: UserWithAccounts) => { return Prisma.defineExtension( (prisma) => { return prisma.$extends({ query: { ...(env.EXPERIMENT_EE_PERMISSION_SYNC_ENABLED === 'true' && hasEntitlement('permission-syncing') ? { repo: { async $allOperations({ args, query }) { const argsWithWhere = args as Record & { where?: Prisma.RepoWhereInput; } argsWithWhere.where = { ...(argsWithWhere.where || {}), ...getRepoPermissionFilterForUser(user), }; return query(args); } } } : {}) } }) }) } /** * Returns a filter for repositories that the user has access to. */ export const getRepoPermissionFilterForUser = (user?: UserWithAccounts): Prisma.RepoWhereInput => { return { OR: [ // Only include repos that are permitted to the user ...((user && user.accounts.length > 0) ? [ { permittedAccounts: { some: { accountId: { in: user.accounts.map(account => account.id), } } } }, ] : []), // or are public. { isPublic: true, } ] } }