CognxSafeTrack
feat: backlog P0βP3 β toast system, payments, tenant isolation, feedback handler, i18n parity
6dd9bad | import { PrismaClient } from '@prisma/client'; | |
| import { getOrganizationId } from './context'; | |
| export const createTenantExtension = (explicitOrganizationId?: string) => { | |
| return { | |
| query: { | |
| $allModels: { | |
| async $allOperations({ model, operation, args, query }: any) { | |
| const organizationId = explicitOrganizationId || getOrganizationId(); | |
| // π¨ EXCEPTION: Certain models should never be filtered by organizationId | |
| const EXCLUDED_MODELS = ['Organization', 'TrainingData', 'NormalizationRule']; | |
| if (EXCLUDED_MODELS.includes(model) || !organizationId) { | |
| return query(args); | |
| } | |
| // List of operations where we want to enforce organizationId | |
| const filteredOperations = [ | |
| 'findMany', | |
| 'findFirst', | |
| 'count', | |
| 'updateMany', | |
| 'deleteMany', | |
| 'aggregate', | |
| 'groupBy' | |
| ]; | |
| if (filteredOperations.includes(operation)) { | |
| args.where = { ...args.where, organizationId }; | |
| } | |
| // Special handling for create: automatically inject organizationId | |
| if (operation === 'create') { | |
| args.data = { ...args.data, organizationId }; | |
| } | |
| // Special handling for upsert: scope where, and inject in create/update | |
| if (operation === 'upsert') { | |
| args.where = { ...args.where, organizationId }; | |
| args.create = { ...args.create, organizationId }; | |
| args.update = { ...args.update, organizationId }; | |
| } | |
| // For update/delete/findUnique | |
| if (['update', 'delete', 'findUnique'].includes(operation)) { | |
| args.where = { ...args.where, organizationId }; | |
| } | |
| return query(args); | |
| }, | |
| }, | |
| }, | |
| }; | |
| }; | |
| /** | |
| * TenantClient exposes all standard PrismaClient delegates plus $forOrganization. | |
| * The double $extends() in the implementation produces a type that TypeScript cannot | |
| * easily infer, so we cast explicitly to this alias so callers don't need `as any`. | |
| */ | |
| export type TenantClient = PrismaClient & { | |
| $forOrganization(organizationId: string): PrismaClient; | |
| }; | |
| /** | |
| * Extends a PrismaClient with a helper to get a tenant-bound client | |
| */ | |
| export function withTenantIsolation(prisma: PrismaClient): TenantClient { | |
| return prisma.$extends({ | |
| client: { | |
| $forOrganization(organizationId: string) { | |
| return prisma.$extends(createTenantExtension(organizationId)); | |
| } | |
| } | |
| }).$extends(createTenantExtension()) as unknown as TenantClient; | |
| } | |