File size: 2,605 Bytes
e289c5c
192979e
e289c5c
 
 
 
 
ffddff2
e289c5c
 
ffddff2
cfbb685
ffddff2
e289c5c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6dd9bad
 
 
 
 
 
 
e289c5c
 
 
 
 
 
 
 
 
 
 
 
 
7b0c22b
 
 
 
 
 
 
 
 
e289c5c
 
 
7b0c22b
e289c5c
 
 
 
 
 
7b0c22b
e289c5c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
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;
}