@open-mercato/core 0.6.4-develop.4038.1.91ce075c8a → 0.6.4-develop.4106.1.12c205a613

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/modules/currencies/acl.js +10 -5
  2. package/dist/modules/currencies/acl.js.map +2 -2
  3. package/dist/modules/directory/acl.js +12 -2
  4. package/dist/modules/directory/acl.js.map +2 -2
  5. package/dist/modules/directory/api/tenants/route.js +48 -34
  6. package/dist/modules/directory/api/tenants/route.js.map +2 -2
  7. package/dist/modules/entities/acl.js +12 -2
  8. package/dist/modules/entities/acl.js.map +2 -2
  9. package/dist/modules/integrations/api/[id]/credentials/route.js +20 -2
  10. package/dist/modules/integrations/api/[id]/credentials/route.js.map +2 -2
  11. package/dist/modules/integrations/lib/credentials-service.js +15 -25
  12. package/dist/modules/integrations/lib/credentials-service.js.map +2 -2
  13. package/dist/modules/query_index/acl.js +12 -2
  14. package/dist/modules/query_index/acl.js.map +2 -2
  15. package/dist/modules/query_index/lib/engine.js +32 -4
  16. package/dist/modules/query_index/lib/engine.js.map +2 -2
  17. package/dist/modules/resources/acl.js +6 -1
  18. package/dist/modules/resources/acl.js.map +2 -2
  19. package/dist/modules/sync_excel/acl.js +6 -1
  20. package/dist/modules/sync_excel/acl.js.map +2 -2
  21. package/package.json +7 -7
  22. package/src/modules/currencies/acl.ts +5 -0
  23. package/src/modules/directory/acl.ts +12 -2
  24. package/src/modules/directory/api/tenants/route.ts +55 -41
  25. package/src/modules/entities/acl.ts +12 -2
  26. package/src/modules/integrations/api/[id]/credentials/route.ts +21 -3
  27. package/src/modules/integrations/lib/credentials-service.ts +15 -33
  28. package/src/modules/query_index/acl.ts +12 -2
  29. package/src/modules/query_index/lib/engine.ts +37 -7
  30. package/src/modules/resources/acl.ts +6 -1
  31. package/src/modules/sync_excel/acl.ts +6 -1
@@ -7,27 +7,32 @@ const features = [
7
7
  {
8
8
  id: "currencies.manage",
9
9
  title: "Manage currencies",
10
- module: "currencies"
10
+ module: "currencies",
11
+ dependsOn: ["currencies.view"]
11
12
  },
12
13
  {
13
14
  id: "currencies.rates.view",
14
15
  title: "View exchange rates",
15
- module: "currencies"
16
+ module: "currencies",
17
+ dependsOn: ["currencies.view"]
16
18
  },
17
19
  {
18
20
  id: "currencies.rates.manage",
19
21
  title: "Manage exchange rates",
20
- module: "currencies"
22
+ module: "currencies",
23
+ dependsOn: ["currencies.rates.view"]
21
24
  },
22
25
  {
23
26
  id: "currencies.fetch.view",
24
27
  title: "View currency fetch configuration",
25
- module: "currencies"
28
+ module: "currencies",
29
+ dependsOn: ["currencies.view"]
26
30
  },
27
31
  {
28
32
  id: "currencies.fetch.manage",
29
33
  title: "Manage currency fetch configuration",
30
- module: "currencies"
34
+ module: "currencies",
35
+ dependsOn: ["currencies.fetch.view"]
31
36
  }
32
37
  ];
33
38
  var acl_default = features;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/currencies/acl.ts"],
4
- "sourcesContent": ["export const features = [\n {\n id: 'currencies.view',\n title: 'View currencies',\n module: 'currencies',\n },\n {\n id: 'currencies.manage',\n title: 'Manage currencies',\n module: 'currencies',\n },\n {\n id: 'currencies.rates.view',\n title: 'View exchange rates',\n module: 'currencies',\n },\n {\n id: 'currencies.rates.manage',\n title: 'Manage exchange rates',\n module: 'currencies',\n },\n {\n id: 'currencies.fetch.view',\n title: 'View currency fetch configuration',\n module: 'currencies',\n },\n {\n id: 'currencies.fetch.manage',\n title: 'Manage currency fetch configuration',\n module: 'currencies',\n },\n]\n\nexport default features\n"],
5
- "mappings": "AAAO,MAAM,WAAW;AAAA,EACtB;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,EACV;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,EACV;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,EACV;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,EACV;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,EACV;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,EACV;AACF;AAEA,IAAO,cAAQ;",
4
+ "sourcesContent": ["export const features = [\n {\n id: 'currencies.view',\n title: 'View currencies',\n module: 'currencies',\n },\n {\n id: 'currencies.manage',\n title: 'Manage currencies',\n module: 'currencies',\n dependsOn: ['currencies.view'],\n },\n {\n id: 'currencies.rates.view',\n title: 'View exchange rates',\n module: 'currencies',\n dependsOn: ['currencies.view'],\n },\n {\n id: 'currencies.rates.manage',\n title: 'Manage exchange rates',\n module: 'currencies',\n dependsOn: ['currencies.rates.view'],\n },\n {\n id: 'currencies.fetch.view',\n title: 'View currency fetch configuration',\n module: 'currencies',\n dependsOn: ['currencies.view'],\n },\n {\n id: 'currencies.fetch.manage',\n title: 'Manage currency fetch configuration',\n module: 'currencies',\n dependsOn: ['currencies.fetch.view'],\n },\n]\n\nexport default features\n"],
5
+ "mappings": "AAAO,MAAM,WAAW;AAAA,EACtB;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,EACV;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,iBAAiB;AAAA,EAC/B;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,iBAAiB;AAAA,EAC/B;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,uBAAuB;AAAA,EACrC;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,iBAAiB;AAAA,EAC/B;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,uBAAuB;AAAA,EACrC;AACF;AAEA,IAAO,cAAQ;",
6
6
  "names": []
7
7
  }
@@ -1,8 +1,18 @@
1
1
  const features = [
2
2
  { id: "directory.tenants.view", title: "View tenants", module: "directory" },
3
- { id: "directory.tenants.manage", title: "Manage tenants", module: "directory" },
3
+ {
4
+ id: "directory.tenants.manage",
5
+ title: "Manage tenants",
6
+ module: "directory",
7
+ dependsOn: ["directory.tenants.view"]
8
+ },
4
9
  { id: "directory.organizations.view", title: "View organizations", module: "directory" },
5
- { id: "directory.organizations.manage", title: "Manage organizations", module: "directory" }
10
+ {
11
+ id: "directory.organizations.manage",
12
+ title: "Manage organizations",
13
+ module: "directory",
14
+ dependsOn: ["directory.organizations.view"]
15
+ }
6
16
  ];
7
17
  var acl_default = features;
8
18
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/directory/acl.ts"],
4
- "sourcesContent": ["export const features = [\n { id: 'directory.tenants.view', title: 'View tenants', module: 'directory' },\n { id: 'directory.tenants.manage', title: 'Manage tenants', module: 'directory' },\n { id: 'directory.organizations.view', title: 'View organizations', module: 'directory' },\n { id: 'directory.organizations.manage', title: 'Manage organizations', module: 'directory' },\n]\n\nexport default features\n"],
5
- "mappings": "AAAO,MAAM,WAAW;AAAA,EACpB,EAAE,IAAI,0BAA0B,OAAO,gBAAgB,QAAQ,YAAY;AAAA,EAC3E,EAAE,IAAI,4BAA4B,OAAO,kBAAkB,QAAQ,YAAY;AAAA,EAC/E,EAAE,IAAI,gCAAgC,OAAO,sBAAsB,QAAQ,YAAY;AAAA,EACvF,EAAE,IAAI,kCAAkC,OAAO,wBAAwB,QAAQ,YAAY;AAC/F;AAEA,IAAO,cAAQ;",
4
+ "sourcesContent": ["export const features = [\n { id: 'directory.tenants.view', title: 'View tenants', module: 'directory' },\n {\n id: 'directory.tenants.manage',\n title: 'Manage tenants',\n module: 'directory',\n dependsOn: ['directory.tenants.view'],\n },\n { id: 'directory.organizations.view', title: 'View organizations', module: 'directory' },\n {\n id: 'directory.organizations.manage',\n title: 'Manage organizations',\n module: 'directory',\n dependsOn: ['directory.organizations.view'],\n },\n]\n\nexport default features\n"],
5
+ "mappings": "AAAO,MAAM,WAAW;AAAA,EACpB,EAAE,IAAI,0BAA0B,OAAO,gBAAgB,QAAQ,YAAY;AAAA,EAC3E;AAAA,IACI,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,wBAAwB;AAAA,EACxC;AAAA,EACA,EAAE,IAAI,gCAAgC,OAAO,sBAAsB,QAAQ,YAAY;AAAA,EACvF;AAAA,IACI,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,8BAA8B;AAAA,EAC9C;AACJ;AAEA,IAAO,cAAQ;",
6
6
  "names": []
7
7
  }
@@ -110,22 +110,6 @@ async function GET(req) {
110
110
  } else {
111
111
  orderBy.name = "ASC";
112
112
  }
113
- const all = await em.find(Tenant, where, { orderBy });
114
- const recordIds = all.map((tenant) => String(tenant.id));
115
- const tenantIdByRecord = {};
116
- const organizationIdByRecord = {};
117
- for (const rid of recordIds) {
118
- tenantIdByRecord[rid] = null;
119
- organizationIdByRecord[rid] = null;
120
- }
121
- const cfValues = recordIds.length ? await loadCustomFieldValues({
122
- em,
123
- entityId: E.directory.tenant,
124
- recordIds,
125
- tenantIdByRecord,
126
- organizationIdByRecord,
127
- tenantFallbacks: auth.tenantId ? [auth.tenantId] : []
128
- }) : {};
129
113
  const rawQuery = Array.from(url.searchParams.keys()).reduce((acc, key) => {
130
114
  const values = url.searchParams.getAll(key);
131
115
  if (!values.length) return acc;
@@ -142,26 +126,56 @@ async function GET(req) {
142
126
  const normalizedKey = rawKey.startsWith("cf:") ? rawKey.slice(3) : rawKey.replace(/^cf_/, "");
143
127
  return [normalizedKey, condition];
144
128
  });
145
- const filtered = cfFilterEntries.length ? all.filter((tenant) => {
146
- const rid = String(tenant.id);
147
- const payload = cfValues[rid] ?? {};
148
- return cfFilterEntries.every(([key, expected]) => {
149
- const value = payload[`cf_${key}`];
150
- if (expected && typeof expected === "object" && !Array.isArray(expected)) {
151
- const maybeIn = expected.$in;
152
- if (Array.isArray(maybeIn)) {
153
- if (value === void 0 || value === null) return false;
154
- if (Array.isArray(value)) return value.some((val) => maybeIn.includes(val));
155
- return maybeIn.includes(value);
156
- }
157
- }
158
- return matchesValue(value, expected);
129
+ const loadCfValues = (tenants) => {
130
+ const recordIds = tenants.map((tenant) => String(tenant.id));
131
+ if (!recordIds.length) return Promise.resolve({});
132
+ const tenantIdByRecord = {};
133
+ const organizationIdByRecord = {};
134
+ for (const rid of recordIds) {
135
+ tenantIdByRecord[rid] = null;
136
+ organizationIdByRecord[rid] = null;
137
+ }
138
+ return loadCustomFieldValues({
139
+ em,
140
+ entityId: E.directory.tenant,
141
+ recordIds,
142
+ tenantIdByRecord,
143
+ organizationIdByRecord,
144
+ tenantFallbacks: auth.tenantId ? [auth.tenantId] : []
159
145
  });
160
- }) : all;
161
- const total = filtered.length;
146
+ };
162
147
  const start = (page - 1) * pageSize;
163
- const paged = filtered.slice(start, start + pageSize);
164
- const items = paged.map((tenant) => {
148
+ let pageTenants;
149
+ let total;
150
+ let cfValues;
151
+ if (cfFilterEntries.length) {
152
+ const all = await em.find(Tenant, where, { orderBy });
153
+ cfValues = await loadCfValues(all);
154
+ const filtered = all.filter((tenant) => {
155
+ const rid = String(tenant.id);
156
+ const payload = cfValues[rid] ?? {};
157
+ return cfFilterEntries.every(([key, expected]) => {
158
+ const value = payload[`cf_${key}`];
159
+ if (expected && typeof expected === "object" && !Array.isArray(expected)) {
160
+ const maybeIn = expected.$in;
161
+ if (Array.isArray(maybeIn)) {
162
+ if (value === void 0 || value === null) return false;
163
+ if (Array.isArray(value)) return value.some((val) => maybeIn.includes(val));
164
+ return maybeIn.includes(value);
165
+ }
166
+ }
167
+ return matchesValue(value, expected);
168
+ });
169
+ });
170
+ total = filtered.length;
171
+ pageTenants = filtered.slice(start, start + pageSize);
172
+ } else {
173
+ const [rows, count] = await em.findAndCount(Tenant, where, { orderBy, limit: pageSize, offset: start });
174
+ total = count;
175
+ pageTenants = rows;
176
+ cfValues = await loadCfValues(rows);
177
+ }
178
+ const items = pageTenants.map((tenant) => {
165
179
  const rid = String(tenant.id);
166
180
  const cf = cfValues[rid] ?? {};
167
181
  return toRow(tenant, cf);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/directory/api/tenants/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { logCrudAccess, makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { Tenant } from '@open-mercato/core/modules/directory/data/entities'\nimport { tenantCreateSchema, tenantUpdateSchema } from '@open-mercato/core/modules/directory/data/validators'\nimport { loadCustomFieldValues, buildCustomFieldFiltersFromQuery } from '@open-mercato/shared/lib/crud/custom-fields'\nimport { E } from '#generated/entities.ids.generated'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { FilterQuery } from '@mikro-orm/core'\nimport { tenantCrudEvents, tenantCrudIndexer } from '@open-mercato/core/modules/directory/commands/tenants'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { directoryTag, directoryErrorSchema, directoryOkSchema, tenantListResponseSchema } from '../openapi'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\n\nconst listQuerySchema = z.object({\n id: z.string().uuid().optional(),\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(100).default(50),\n search: z.string().optional(),\n sortField: z.enum(['name', 'createdAt', 'updatedAt']).optional(),\n sortDir: z.enum(['asc', 'desc']).optional(),\n isActive: z.enum(['true', 'false']).optional(),\n}).passthrough()\n\ntype TenantRow = {\n id: string\n name: string\n isActive: boolean\n createdAt: string | null\n updatedAt: string | null\n} & Record<string, unknown>\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['directory.tenants.view'] },\n POST: { requireAuth: true, requireFeatures: ['directory.tenants.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['directory.tenants.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['directory.tenants.manage'] },\n}\n\nexport const metadata = routeMetadata\n\nconst rawBodySchema = z.object({}).passthrough()\ntype CrudInput = Record<string, unknown>\n\nconst crud = makeCrudRoute<CrudInput, CrudInput, Record<string, unknown>>({\n metadata: routeMetadata,\n orm: {\n entity: Tenant,\n idField: 'id',\n orgField: null,\n tenantField: null,\n softDeleteField: 'deletedAt',\n },\n events: tenantCrudEvents,\n indexer: tenantCrudIndexer,\n actions: {\n create: {\n commandId: 'directory.tenants.create',\n schema: rawBodySchema,\n mapInput: ({ parsed }) => parsed,\n response: ({ result }) => ({ id: String(result.id) }),\n status: 201,\n },\n update: {\n commandId: 'directory.tenants.update',\n schema: rawBodySchema,\n mapInput: ({ parsed }) => parsed,\n response: () => ({ ok: true }),\n },\n delete: {\n commandId: 'directory.tenants.delete',\n response: () => ({ ok: true }),\n },\n },\n})\n\nconst toRow = (tenant: Tenant, cf: Record<string, unknown>): TenantRow => ({\n id: String(tenant.id),\n name: String(tenant.name),\n isActive: !!tenant.isActive,\n createdAt: tenant.createdAt ? tenant.createdAt.toISOString() : null,\n updatedAt: tenant.updatedAt ? tenant.updatedAt.toISOString() : null,\n ...cf,\n})\n\nconst matchesValue = (current: unknown, expected: unknown): boolean => {\n if (Array.isArray(current)) return current.some((item) => item === expected)\n return current === expected\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ items: [], total: 0, page: 1, pageSize: 50, totalPages: 1 }, { status: 401 })\n }\n\n const url = new URL(req.url)\n const parsed = listQuerySchema.safeParse({\n id: url.searchParams.get('id') ?? undefined,\n page: url.searchParams.get('page') ?? undefined,\n pageSize: url.searchParams.get('pageSize') ?? undefined,\n search: url.searchParams.get('search') ?? undefined,\n sortField: url.searchParams.get('sortField') ?? undefined,\n sortDir: url.searchParams.get('sortDir') ?? undefined,\n isActive: url.searchParams.get('isActive') ?? undefined,\n })\n if (!parsed.success) {\n return NextResponse.json(\n { error: 'Invalid query parameters', details: parsed.error.flatten() },\n { status: 400 },\n )\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n\n const { id, page, pageSize, search, sortField, sortDir, isActive } = parsed.data\n const where: FilterQuery<Tenant> = { deletedAt: null }\n if (id) where.id = id\n if (search) {\n Object.assign(where, { name: { $ilike: `%${escapeLikePattern(search)}%` } })\n }\n const active = parseBooleanToken(isActive)\n if (active !== null) where.isActive = active\n\n const fieldMap: Record<string, string> = { name: 'name', createdAt: 'createdAt', updatedAt: 'updatedAt' }\n const orderBy: Record<string, 'ASC' | 'DESC'> = {}\n if (sortField) {\n const mapped = fieldMap[sortField] || 'name'\n orderBy[mapped] = sortDir === 'desc' ? 'DESC' : 'ASC'\n } else {\n orderBy.name = 'ASC'\n }\n\n const all = await em.find(Tenant, where, { orderBy })\n const recordIds = all.map((tenant) => String(tenant.id))\n\n const tenantIdByRecord: Record<string, string | null> = {}\n const organizationIdByRecord: Record<string, string | null> = {}\n for (const rid of recordIds) {\n tenantIdByRecord[rid] = null\n organizationIdByRecord[rid] = null\n }\n\n const cfValues = recordIds.length\n ? await loadCustomFieldValues({\n em,\n entityId: E.directory.tenant,\n recordIds,\n tenantIdByRecord,\n organizationIdByRecord,\n tenantFallbacks: auth.tenantId ? [auth.tenantId] : [],\n })\n : {}\n\n const rawQuery = Array.from(url.searchParams.keys()).reduce<Record<string, unknown>>((acc, key) => {\n const values = url.searchParams.getAll(key)\n if (!values.length) return acc\n acc[key] = values.length === 1 ? values[0] : values\n return acc\n }, {})\n\n const cfFilters = await buildCustomFieldFiltersFromQuery({\n entityId: E.directory.tenant,\n query: rawQuery,\n em,\n tenantId: auth.tenantId ?? null,\n })\n const cfFilterEntries = Object.entries(cfFilters).map(([rawKey, condition]) => {\n const normalizedKey = rawKey.startsWith('cf:') ? rawKey.slice(3) : rawKey.replace(/^cf_/, '')\n return [normalizedKey, condition] as const\n })\n\n const filtered = cfFilterEntries.length\n ? all.filter((tenant) => {\n const rid = String(tenant.id)\n const payload = cfValues[rid] ?? {}\n return cfFilterEntries.every(([key, expected]) => {\n const value = payload[`cf_${key}`]\n if (expected && typeof expected === 'object' && !Array.isArray(expected)) {\n const maybeIn = (expected as { $in?: unknown[] }).$in\n if (Array.isArray(maybeIn)) {\n if (value === undefined || value === null) return false\n if (Array.isArray(value)) return value.some((val) => maybeIn.includes(val))\n return maybeIn.includes(value)\n }\n }\n return matchesValue(value, expected)\n })\n })\n : all\n\n const total = filtered.length\n const start = (page - 1) * pageSize\n const paged = filtered.slice(start, start + pageSize)\n const items = paged.map((tenant) => {\n const rid = String(tenant.id)\n const cf = cfValues[rid] ?? {}\n return toRow(tenant, cf)\n })\n const totalPages = Math.max(1, Math.ceil(total / pageSize))\n\n await logCrudAccess({\n container,\n auth,\n request: req,\n items,\n idField: 'id',\n resourceKind: 'directory.tenant',\n organizationId: null,\n tenantId: auth.tenantId ?? null,\n query: rawQuery,\n accessType: id ? 'read:item' : undefined,\n })\n\n return NextResponse.json({ items, total, page, pageSize, totalPages })\n}\n\nexport const POST = crud.POST\nexport const PUT = crud.PUT\nexport const DELETE = crud.DELETE\n\nconst tenantCreateResponseSchema = z.object({\n id: z.string().uuid(),\n})\n\nconst tenantDeleteRequestSchema = z.object({\n id: z.string().uuid(),\n})\n\nconst tenantGetDoc: OpenApiMethodDoc = {\n summary: 'List tenants',\n description: 'Returns tenants visible to the current user with optional search and pagination.',\n tags: [directoryTag],\n query: listQuerySchema,\n responses: [\n { status: 200, description: 'Paged list of tenants.', schema: tenantListResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid query parameters', schema: directoryErrorSchema },\n { status: 401, description: 'Authentication required', schema: directoryErrorSchema },\n ],\n}\n\nconst tenantPostDoc: OpenApiMethodDoc = {\n summary: 'Create tenant',\n description: 'Creates a new tenant and returns its identifier.',\n tags: [directoryTag],\n requestBody: {\n contentType: 'application/json',\n schema: tenantCreateSchema,\n description: 'Tenant name and optional activation flag.',\n },\n responses: [\n { status: 201, description: 'Tenant created.', schema: tenantCreateResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: directoryErrorSchema },\n { status: 401, description: 'Authentication required', schema: directoryErrorSchema },\n { status: 403, description: 'Missing directory.tenants.manage feature', schema: directoryErrorSchema },\n ],\n}\n\nconst tenantPutDoc: OpenApiMethodDoc = {\n summary: 'Update tenant',\n description: 'Updates tenant properties such as name or activation state.',\n tags: [directoryTag],\n requestBody: {\n contentType: 'application/json',\n schema: tenantUpdateSchema,\n description: 'Tenant identifier with fields to update.',\n },\n responses: [\n { status: 200, description: 'Tenant updated.', schema: directoryOkSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: directoryErrorSchema },\n { status: 401, description: 'Authentication required', schema: directoryErrorSchema },\n { status: 403, description: 'Missing directory.tenants.manage feature', schema: directoryErrorSchema },\n ],\n}\n\nconst tenantDeleteDoc: OpenApiMethodDoc = {\n summary: 'Delete tenant',\n description: 'Soft deletes the tenant identified by id.',\n tags: [directoryTag],\n requestBody: {\n contentType: 'application/json',\n schema: tenantDeleteRequestSchema,\n description: 'Identifier of the tenant to remove.',\n },\n responses: [\n { status: 200, description: 'Tenant removed.', schema: directoryOkSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: directoryErrorSchema },\n { status: 401, description: 'Authentication required', schema: directoryErrorSchema },\n { status: 403, description: 'Missing directory.tenants.manage feature', schema: directoryErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: directoryTag,\n summary: 'Manage tenants',\n methods: {\n GET: tenantGetDoc,\n POST: tenantPostDoc,\n PUT: tenantPutDoc,\n DELETE: tenantDeleteDoc,\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { logCrudAccess, makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { Tenant } from '@open-mercato/core/modules/directory/data/entities'\nimport { tenantCreateSchema, tenantUpdateSchema } from '@open-mercato/core/modules/directory/data/validators'\nimport { loadCustomFieldValues, buildCustomFieldFiltersFromQuery } from '@open-mercato/shared/lib/crud/custom-fields'\nimport { E } from '#generated/entities.ids.generated'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { FilterQuery } from '@mikro-orm/core'\nimport { tenantCrudEvents, tenantCrudIndexer } from '@open-mercato/core/modules/directory/commands/tenants'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { directoryTag, directoryErrorSchema, directoryOkSchema, tenantListResponseSchema } from '../openapi'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\n\nconst listQuerySchema = z.object({\n id: z.string().uuid().optional(),\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(100).default(50),\n search: z.string().optional(),\n sortField: z.enum(['name', 'createdAt', 'updatedAt']).optional(),\n sortDir: z.enum(['asc', 'desc']).optional(),\n isActive: z.enum(['true', 'false']).optional(),\n}).passthrough()\n\ntype TenantRow = {\n id: string\n name: string\n isActive: boolean\n createdAt: string | null\n updatedAt: string | null\n} & Record<string, unknown>\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['directory.tenants.view'] },\n POST: { requireAuth: true, requireFeatures: ['directory.tenants.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['directory.tenants.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['directory.tenants.manage'] },\n}\n\nexport const metadata = routeMetadata\n\nconst rawBodySchema = z.object({}).passthrough()\ntype CrudInput = Record<string, unknown>\n\nconst crud = makeCrudRoute<CrudInput, CrudInput, Record<string, unknown>>({\n metadata: routeMetadata,\n orm: {\n entity: Tenant,\n idField: 'id',\n orgField: null,\n tenantField: null,\n softDeleteField: 'deletedAt',\n },\n events: tenantCrudEvents,\n indexer: tenantCrudIndexer,\n actions: {\n create: {\n commandId: 'directory.tenants.create',\n schema: rawBodySchema,\n mapInput: ({ parsed }) => parsed,\n response: ({ result }) => ({ id: String(result.id) }),\n status: 201,\n },\n update: {\n commandId: 'directory.tenants.update',\n schema: rawBodySchema,\n mapInput: ({ parsed }) => parsed,\n response: () => ({ ok: true }),\n },\n delete: {\n commandId: 'directory.tenants.delete',\n response: () => ({ ok: true }),\n },\n },\n})\n\nconst toRow = (tenant: Tenant, cf: Record<string, unknown>): TenantRow => ({\n id: String(tenant.id),\n name: String(tenant.name),\n isActive: !!tenant.isActive,\n createdAt: tenant.createdAt ? tenant.createdAt.toISOString() : null,\n updatedAt: tenant.updatedAt ? tenant.updatedAt.toISOString() : null,\n ...cf,\n})\n\nconst matchesValue = (current: unknown, expected: unknown): boolean => {\n if (Array.isArray(current)) return current.some((item) => item === expected)\n return current === expected\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ items: [], total: 0, page: 1, pageSize: 50, totalPages: 1 }, { status: 401 })\n }\n\n const url = new URL(req.url)\n const parsed = listQuerySchema.safeParse({\n id: url.searchParams.get('id') ?? undefined,\n page: url.searchParams.get('page') ?? undefined,\n pageSize: url.searchParams.get('pageSize') ?? undefined,\n search: url.searchParams.get('search') ?? undefined,\n sortField: url.searchParams.get('sortField') ?? undefined,\n sortDir: url.searchParams.get('sortDir') ?? undefined,\n isActive: url.searchParams.get('isActive') ?? undefined,\n })\n if (!parsed.success) {\n return NextResponse.json(\n { error: 'Invalid query parameters', details: parsed.error.flatten() },\n { status: 400 },\n )\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n\n const { id, page, pageSize, search, sortField, sortDir, isActive } = parsed.data\n const where: FilterQuery<Tenant> = { deletedAt: null }\n if (id) where.id = id\n if (search) {\n Object.assign(where, { name: { $ilike: `%${escapeLikePattern(search)}%` } })\n }\n const active = parseBooleanToken(isActive)\n if (active !== null) where.isActive = active\n\n const fieldMap: Record<string, string> = { name: 'name', createdAt: 'createdAt', updatedAt: 'updatedAt' }\n const orderBy: Record<string, 'ASC' | 'DESC'> = {}\n if (sortField) {\n const mapped = fieldMap[sortField] || 'name'\n orderBy[mapped] = sortDir === 'desc' ? 'DESC' : 'ASC'\n } else {\n orderBy.name = 'ASC'\n }\n\n const rawQuery = Array.from(url.searchParams.keys()).reduce<Record<string, unknown>>((acc, key) => {\n const values = url.searchParams.getAll(key)\n if (!values.length) return acc\n acc[key] = values.length === 1 ? values[0] : values\n return acc\n }, {})\n\n const cfFilters = await buildCustomFieldFiltersFromQuery({\n entityId: E.directory.tenant,\n query: rawQuery,\n em,\n tenantId: auth.tenantId ?? null,\n })\n const cfFilterEntries = Object.entries(cfFilters).map(([rawKey, condition]) => {\n const normalizedKey = rawKey.startsWith('cf:') ? rawKey.slice(3) : rawKey.replace(/^cf_/, '')\n return [normalizedKey, condition] as const\n })\n\n const loadCfValues = (tenants: Tenant[]): Promise<Record<string, Record<string, unknown>>> => {\n const recordIds = tenants.map((tenant) => String(tenant.id))\n if (!recordIds.length) return Promise.resolve({})\n const tenantIdByRecord: Record<string, string | null> = {}\n const organizationIdByRecord: Record<string, string | null> = {}\n for (const rid of recordIds) {\n tenantIdByRecord[rid] = null\n organizationIdByRecord[rid] = null\n }\n return loadCustomFieldValues({\n em,\n entityId: E.directory.tenant,\n recordIds,\n tenantIdByRecord,\n organizationIdByRecord,\n tenantFallbacks: auth.tenantId ? [auth.tenantId] : [],\n })\n }\n\n const start = (page - 1) * pageSize\n\n let pageTenants: Tenant[]\n let total: number\n let cfValues: Record<string, Record<string, unknown>>\n\n if (cfFilterEntries.length) {\n // Custom-field filters are matched in JS against separately-loaded values,\n // so the full result set must be fetched before filtering and paging.\n const all = await em.find(Tenant, where, { orderBy })\n cfValues = await loadCfValues(all)\n const filtered = all.filter((tenant) => {\n const rid = String(tenant.id)\n const payload = cfValues[rid] ?? {}\n return cfFilterEntries.every(([key, expected]) => {\n const value = payload[`cf_${key}`]\n if (expected && typeof expected === 'object' && !Array.isArray(expected)) {\n const maybeIn = (expected as { $in?: unknown[] }).$in\n if (Array.isArray(maybeIn)) {\n if (value === undefined || value === null) return false\n if (Array.isArray(value)) return value.some((val) => maybeIn.includes(val))\n return maybeIn.includes(value)\n }\n }\n return matchesValue(value, expected)\n })\n })\n total = filtered.length\n pageTenants = filtered.slice(start, start + pageSize)\n } else {\n // No JS-side filtering applies, so pagination is pushed to the database\n // instead of fetching the whole table and slicing in memory.\n const [rows, count] = await em.findAndCount(Tenant, where, { orderBy, limit: pageSize, offset: start })\n total = count\n pageTenants = rows\n cfValues = await loadCfValues(rows)\n }\n\n const items = pageTenants.map((tenant) => {\n const rid = String(tenant.id)\n const cf = cfValues[rid] ?? {}\n return toRow(tenant, cf)\n })\n const totalPages = Math.max(1, Math.ceil(total / pageSize))\n\n await logCrudAccess({\n container,\n auth,\n request: req,\n items,\n idField: 'id',\n resourceKind: 'directory.tenant',\n organizationId: null,\n tenantId: auth.tenantId ?? null,\n query: rawQuery,\n accessType: id ? 'read:item' : undefined,\n })\n\n return NextResponse.json({ items, total, page, pageSize, totalPages })\n}\n\nexport const POST = crud.POST\nexport const PUT = crud.PUT\nexport const DELETE = crud.DELETE\n\nconst tenantCreateResponseSchema = z.object({\n id: z.string().uuid(),\n})\n\nconst tenantDeleteRequestSchema = z.object({\n id: z.string().uuid(),\n})\n\nconst tenantGetDoc: OpenApiMethodDoc = {\n summary: 'List tenants',\n description: 'Returns tenants visible to the current user with optional search and pagination.',\n tags: [directoryTag],\n query: listQuerySchema,\n responses: [\n { status: 200, description: 'Paged list of tenants.', schema: tenantListResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid query parameters', schema: directoryErrorSchema },\n { status: 401, description: 'Authentication required', schema: directoryErrorSchema },\n ],\n}\n\nconst tenantPostDoc: OpenApiMethodDoc = {\n summary: 'Create tenant',\n description: 'Creates a new tenant and returns its identifier.',\n tags: [directoryTag],\n requestBody: {\n contentType: 'application/json',\n schema: tenantCreateSchema,\n description: 'Tenant name and optional activation flag.',\n },\n responses: [\n { status: 201, description: 'Tenant created.', schema: tenantCreateResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: directoryErrorSchema },\n { status: 401, description: 'Authentication required', schema: directoryErrorSchema },\n { status: 403, description: 'Missing directory.tenants.manage feature', schema: directoryErrorSchema },\n ],\n}\n\nconst tenantPutDoc: OpenApiMethodDoc = {\n summary: 'Update tenant',\n description: 'Updates tenant properties such as name or activation state.',\n tags: [directoryTag],\n requestBody: {\n contentType: 'application/json',\n schema: tenantUpdateSchema,\n description: 'Tenant identifier with fields to update.',\n },\n responses: [\n { status: 200, description: 'Tenant updated.', schema: directoryOkSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: directoryErrorSchema },\n { status: 401, description: 'Authentication required', schema: directoryErrorSchema },\n { status: 403, description: 'Missing directory.tenants.manage feature', schema: directoryErrorSchema },\n ],\n}\n\nconst tenantDeleteDoc: OpenApiMethodDoc = {\n summary: 'Delete tenant',\n description: 'Soft deletes the tenant identified by id.',\n tags: [directoryTag],\n requestBody: {\n contentType: 'application/json',\n schema: tenantDeleteRequestSchema,\n description: 'Identifier of the tenant to remove.',\n },\n responses: [\n { status: 200, description: 'Tenant removed.', schema: directoryOkSchema },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: directoryErrorSchema },\n { status: 401, description: 'Authentication required', schema: directoryErrorSchema },\n { status: 403, description: 'Missing directory.tenants.manage feature', schema: directoryErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: directoryTag,\n summary: 'Manage tenants',\n methods: {\n GET: tenantGetDoc,\n POST: tenantPostDoc,\n PUT: tenantPutDoc,\n DELETE: tenantDeleteDoc,\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,8 +1,18 @@
1
1
  const features = [
2
2
  { id: "entities.definitions.view", title: "View custom field definitions", module: "entities" },
3
- { id: "entities.definitions.manage", title: "Manage custom field definitions", module: "entities" },
3
+ {
4
+ id: "entities.definitions.manage",
5
+ title: "Manage custom field definitions",
6
+ module: "entities",
7
+ dependsOn: ["entities.definitions.view"]
8
+ },
4
9
  { id: "entities.records.view", title: "View records", module: "entities" },
5
- { id: "entities.records.manage", title: "Manage records", module: "entities" }
10
+ {
11
+ id: "entities.records.manage",
12
+ title: "Manage records",
13
+ module: "entities",
14
+ dependsOn: ["entities.records.view"]
15
+ }
6
16
  ];
7
17
  var acl_default = features;
8
18
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/entities/acl.ts"],
4
- "sourcesContent": ["export const features = [\n { id: 'entities.definitions.view', title: 'View custom field definitions', module: 'entities' },\n { id: 'entities.definitions.manage', title: 'Manage custom field definitions', module: 'entities' },\n { id: 'entities.records.view', title: 'View records', module: 'entities' },\n { id: 'entities.records.manage', title: 'Manage records', module: 'entities' },\n]\n\nexport default features\n"],
5
- "mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,EAAE,IAAI,6BAA6B,OAAO,iCAAiC,QAAQ,WAAW;AAAA,EAC9F,EAAE,IAAI,+BAA+B,OAAO,mCAAmC,QAAQ,WAAW;AAAA,EAClG,EAAE,IAAI,yBAAyB,OAAO,gBAAgB,QAAQ,WAAW;AAAA,EACzE,EAAE,IAAI,2BAA2B,OAAO,kBAAkB,QAAQ,WAAW;AAC/E;AAEA,IAAO,cAAQ;",
4
+ "sourcesContent": ["export const features = [\n { id: 'entities.definitions.view', title: 'View custom field definitions', module: 'entities' },\n {\n id: 'entities.definitions.manage',\n title: 'Manage custom field definitions',\n module: 'entities',\n dependsOn: ['entities.definitions.view'],\n },\n { id: 'entities.records.view', title: 'View records', module: 'entities' },\n {\n id: 'entities.records.manage',\n title: 'Manage records',\n module: 'entities',\n dependsOn: ['entities.records.view'],\n },\n]\n\nexport default features\n"],
5
+ "mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,EAAE,IAAI,6BAA6B,OAAO,iCAAiC,QAAQ,WAAW;AAAA,EAC9F;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,2BAA2B;AAAA,EACzC;AAAA,EACA,EAAE,IAAI,yBAAyB,OAAO,gBAAgB,QAAQ,WAAW;AAAA,EACzE;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,uBAAuB;AAAA,EACrC;AACF;AAEA,IAAO,cAAQ;",
6
6
  "names": []
7
7
  }
@@ -5,6 +5,9 @@ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
5
5
  import { getIntegration } from "@open-mercato/shared/modules/integrations/types";
6
6
  import { emitIntegrationsEvent } from "../../../events.js";
7
7
  import { saveCredentialsSchema } from "../../../data/validators.js";
8
+ import {
9
+ isCredentialsEncryptionUnavailableError
10
+ } from "../../../lib/credentials-service.js";
8
11
  import {
9
12
  resolveUserFeatures,
10
13
  runIntegrationMutationGuardAfterSuccess,
@@ -43,7 +46,15 @@ async function GET(req, ctx) {
43
46
  const container = await createRequestContainer();
44
47
  const credentialsService = container.resolve("integrationCredentialsService");
45
48
  const scope = { organizationId: auth.orgId, tenantId: auth.tenantId };
46
- const values = await credentialsService.resolve(integration.id, scope);
49
+ let values;
50
+ try {
51
+ values = await credentialsService.resolve(integration.id, scope);
52
+ } catch (error) {
53
+ if (isCredentialsEncryptionUnavailableError(error)) {
54
+ return NextResponse.json({ error: "Integration credentials encryption is unavailable" }, { status: 503 });
55
+ }
56
+ throw error;
57
+ }
47
58
  return NextResponse.json({
48
59
  integrationId: integration.id,
49
60
  schema: credentialsService.getSchema(integration.id),
@@ -99,7 +110,14 @@ async function PUT(req, ctx) {
99
110
  }
100
111
  const credentialsService = container.resolve("integrationCredentialsService");
101
112
  const scope = { organizationId: auth.orgId, tenantId: auth.tenantId };
102
- await credentialsService.save(integration.id, payloadData.credentials, scope);
113
+ try {
114
+ await credentialsService.save(integration.id, payloadData.credentials, scope);
115
+ } catch (error) {
116
+ if (isCredentialsEncryptionUnavailableError(error)) {
117
+ return NextResponse.json({ error: "Integration credentials encryption is unavailable" }, { status: 503 });
118
+ }
119
+ throw error;
120
+ }
103
121
  await emitIntegrationsEvent("integrations.credentials.updated", {
104
122
  integrationId: integration.id,
105
123
  tenantId: auth.tenantId,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/integrations/api/%5Bid%5D/credentials/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getIntegration } from '@open-mercato/shared/modules/integrations/types'\nimport { emitIntegrationsEvent } from '../../../events'\nimport { saveCredentialsSchema } from '../../../data/validators'\nimport type { CredentialsService } from '../../../lib/credentials-service'\nimport {\n resolveUserFeatures,\n runIntegrationMutationGuardAfterSuccess,\n runIntegrationMutationGuards,\n} from '../../guards'\n\nconst idParamsSchema = z.object({ id: z.string().min(1) })\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['integrations.credentials.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['integrations.credentials.manage'] },\n}\n\nexport const openApi = {\n tags: ['Integrations'],\n summary: 'Get or save integration credentials',\n}\n\nfunction resolveParams(ctx: { params?: Promise<{ id?: string }> | { id?: string } }): Promise<{ id?: string } | undefined> | { id?: string } | undefined {\n if (!ctx.params) return undefined\n if (typeof (ctx.params as Promise<unknown>).then === 'function') {\n return ctx.params as Promise<{ id?: string }>\n }\n return ctx.params as { id?: string }\n}\n\nexport async function GET(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const rawParams = await resolveParams(ctx)\n const parsedParams = idParamsSchema.safeParse(rawParams)\n if (!parsedParams.success) {\n return NextResponse.json({ error: 'Invalid integration id' }, { status: 400 })\n }\n\n const integration = getIntegration(parsedParams.data.id)\n if (!integration) {\n return NextResponse.json({ error: 'Integration not found' }, { status: 404 })\n }\n\n const container = await createRequestContainer()\n const credentialsService = container.resolve('integrationCredentialsService') as CredentialsService\n const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }\n\n const values = await credentialsService.resolve(integration.id, scope)\n\n return NextResponse.json({\n integrationId: integration.id,\n schema: credentialsService.getSchema(integration.id),\n credentials: values ?? {},\n })\n}\n\nexport async function PUT(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const rawParams = await resolveParams(ctx)\n const parsedParams = idParamsSchema.safeParse(rawParams)\n if (!parsedParams.success) {\n return NextResponse.json({ error: 'Invalid integration id' }, { status: 400 })\n }\n\n const integration = getIntegration(parsedParams.data.id)\n if (!integration) {\n return NextResponse.json({ error: 'Integration not found' }, { status: 404 })\n }\n\n const payload = await req.json().catch(() => null)\n const parsedBody = saveCredentialsSchema.safeParse(payload)\n if (!parsedBody.success) {\n return NextResponse.json({ error: 'Invalid credentials payload', details: parsedBody.error.flatten() }, { status: 422 })\n }\n\n const container = await createRequestContainer()\n const guardResult = await runIntegrationMutationGuards(\n container,\n {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub ?? '',\n resourceKind: 'integrations.integration',\n resourceId: integration.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: parsedBody.data as Record<string, unknown>,\n },\n resolveUserFeatures(auth),\n )\n if (!guardResult.ok) {\n return NextResponse.json(guardResult.errorBody ?? { error: 'Operation blocked by guard' }, { status: guardResult.errorStatus ?? 422 })\n }\n\n let payloadData = parsedBody.data\n if (guardResult.modifiedPayload) {\n const mergedPayload = { ...parsedBody.data, ...guardResult.modifiedPayload }\n const reparsed = saveCredentialsSchema.safeParse(mergedPayload)\n if (!reparsed.success) {\n return NextResponse.json({ error: 'Invalid credentials payload after guard transform', details: reparsed.error.flatten() }, { status: 422 })\n }\n payloadData = reparsed.data\n }\n\n const credentialsService = container.resolve('integrationCredentialsService') as CredentialsService\n const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }\n\n await credentialsService.save(integration.id, payloadData.credentials, scope)\n\n await emitIntegrationsEvent('integrations.credentials.updated', {\n integrationId: integration.id,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub,\n })\n\n await runIntegrationMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub ?? '',\n resourceKind: 'integrations.integration',\n resourceId: integration.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n\n return NextResponse.json({ ok: true })\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getIntegration } from '@open-mercato/shared/modules/integrations/types'\nimport { emitIntegrationsEvent } from '../../../events'\nimport { saveCredentialsSchema } from '../../../data/validators'\nimport {\n isCredentialsEncryptionUnavailableError,\n type CredentialsService,\n} from '../../../lib/credentials-service'\nimport {\n resolveUserFeatures,\n runIntegrationMutationGuardAfterSuccess,\n runIntegrationMutationGuards,\n} from '../../guards'\n\nconst idParamsSchema = z.object({ id: z.string().min(1) })\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['integrations.credentials.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['integrations.credentials.manage'] },\n}\n\nexport const openApi = {\n tags: ['Integrations'],\n summary: 'Get or save integration credentials',\n}\n\nfunction resolveParams(ctx: { params?: Promise<{ id?: string }> | { id?: string } }): Promise<{ id?: string } | undefined> | { id?: string } | undefined {\n if (!ctx.params) return undefined\n if (typeof (ctx.params as Promise<unknown>).then === 'function') {\n return ctx.params as Promise<{ id?: string }>\n }\n return ctx.params as { id?: string }\n}\n\nexport async function GET(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const rawParams = await resolveParams(ctx)\n const parsedParams = idParamsSchema.safeParse(rawParams)\n if (!parsedParams.success) {\n return NextResponse.json({ error: 'Invalid integration id' }, { status: 400 })\n }\n\n const integration = getIntegration(parsedParams.data.id)\n if (!integration) {\n return NextResponse.json({ error: 'Integration not found' }, { status: 404 })\n }\n\n const container = await createRequestContainer()\n const credentialsService = container.resolve('integrationCredentialsService') as CredentialsService\n const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }\n\n let values: Record<string, unknown> | null\n try {\n values = await credentialsService.resolve(integration.id, scope)\n } catch (error) {\n if (isCredentialsEncryptionUnavailableError(error)) {\n return NextResponse.json({ error: 'Integration credentials encryption is unavailable' }, { status: 503 })\n }\n throw error\n }\n\n return NextResponse.json({\n integrationId: integration.id,\n schema: credentialsService.getSchema(integration.id),\n credentials: values ?? {},\n })\n}\n\nexport async function PUT(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const rawParams = await resolveParams(ctx)\n const parsedParams = idParamsSchema.safeParse(rawParams)\n if (!parsedParams.success) {\n return NextResponse.json({ error: 'Invalid integration id' }, { status: 400 })\n }\n\n const integration = getIntegration(parsedParams.data.id)\n if (!integration) {\n return NextResponse.json({ error: 'Integration not found' }, { status: 404 })\n }\n\n const payload = await req.json().catch(() => null)\n const parsedBody = saveCredentialsSchema.safeParse(payload)\n if (!parsedBody.success) {\n return NextResponse.json({ error: 'Invalid credentials payload', details: parsedBody.error.flatten() }, { status: 422 })\n }\n\n const container = await createRequestContainer()\n const guardResult = await runIntegrationMutationGuards(\n container,\n {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub ?? '',\n resourceKind: 'integrations.integration',\n resourceId: integration.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: parsedBody.data as Record<string, unknown>,\n },\n resolveUserFeatures(auth),\n )\n if (!guardResult.ok) {\n return NextResponse.json(guardResult.errorBody ?? { error: 'Operation blocked by guard' }, { status: guardResult.errorStatus ?? 422 })\n }\n\n let payloadData = parsedBody.data\n if (guardResult.modifiedPayload) {\n const mergedPayload = { ...parsedBody.data, ...guardResult.modifiedPayload }\n const reparsed = saveCredentialsSchema.safeParse(mergedPayload)\n if (!reparsed.success) {\n return NextResponse.json({ error: 'Invalid credentials payload after guard transform', details: reparsed.error.flatten() }, { status: 422 })\n }\n payloadData = reparsed.data\n }\n\n const credentialsService = container.resolve('integrationCredentialsService') as CredentialsService\n const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }\n\n try {\n await credentialsService.save(integration.id, payloadData.credentials, scope)\n } catch (error) {\n if (isCredentialsEncryptionUnavailableError(error)) {\n return NextResponse.json({ error: 'Integration credentials encryption is unavailable' }, { status: 503 })\n }\n throw error\n }\n\n await emitIntegrationsEvent('integrations.credentials.updated', {\n integrationId: integration.id,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub,\n })\n\n await runIntegrationMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub ?? '',\n resourceKind: 'integrations.integration',\n resourceId: integration.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n\n return NextResponse.json({ ok: true })\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,4 +1,3 @@
1
- import crypto from "node:crypto";
2
1
  import { decryptWithAesGcm, encryptWithAesGcm } from "@open-mercato/shared/lib/encryption/aes";
3
2
  import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
4
3
  import { createKmsService } from "@open-mercato/shared/lib/encryption/kms";
@@ -11,7 +10,16 @@ import {
11
10
  import { EncryptionMap } from "../../entities/data/entities.js";
12
11
  import { IntegrationCredentials } from "../data/entities.js";
13
12
  const ENCRYPTED_CREDENTIALS_BLOB_KEY = "__om_encrypted_credentials_blob_v1";
14
- const DERIVED_KEY_CONTEXT = "integrations.credentials";
13
+ const CREDENTIALS_ENCRYPTION_UNAVAILABLE_MESSAGE = "Integration credentials encryption key is unavailable. Configure Vault KMS or TENANT_DATA_ENCRYPTION_FALLBACK_KEY.";
14
+ class CredentialsEncryptionUnavailableError extends Error {
15
+ constructor() {
16
+ super(CREDENTIALS_ENCRYPTION_UNAVAILABLE_MESSAGE);
17
+ this.name = "CredentialsEncryptionUnavailableError";
18
+ }
19
+ }
20
+ function isCredentialsEncryptionUnavailableError(error) {
21
+ return error instanceof CredentialsEncryptionUnavailableError;
22
+ }
15
23
  function isRecordValue(value) {
16
24
  return !!value && typeof value === "object" && !Array.isArray(value);
17
25
  }
@@ -21,26 +29,6 @@ function normalizeCredentialsRecord(value) {
21
29
  const parsed = parseDecryptedFieldValue(value);
22
30
  return isRecordValue(parsed) ? parsed : {};
23
31
  }
24
- function resolveFallbackEncryptionSecret() {
25
- const candidates = [
26
- process.env.TENANT_DATA_ENCRYPTION_FALLBACK_KEY,
27
- process.env.TENANT_DATA_ENCRYPTION_KEY,
28
- process.env.AUTH_SECRET,
29
- process.env.NEXTAUTH_SECRET
30
- ];
31
- for (const value of candidates) {
32
- const normalized = value?.trim();
33
- if (normalized) return normalized;
34
- }
35
- if (process.env.NODE_ENV !== "production") return "om-dev-tenant-encryption";
36
- console.warn(
37
- "[integrations.credentials] No encryption secret configured; using emergency fallback secret. Configure TENANT_DATA_ENCRYPTION_FALLBACK_KEY immediately."
38
- );
39
- return "om-emergency-fallback-rotate-me";
40
- }
41
- function deriveDekFromSecret(secret, tenantId) {
42
- return crypto.createHash("sha256").update(`${DERIVED_KEY_CONTEXT}:${tenantId}:${secret}`).digest().toString("base64");
43
- }
44
32
  function createCredentialsService(em) {
45
33
  const credentialsEncryptionSpec = [{ field: "credentials" }];
46
34
  async function ensureCredentialsEncryptionMap(scope) {
@@ -78,7 +66,7 @@ function createCredentialsService(em) {
78
66
  if (existing?.key) return existing.key;
79
67
  const created = await kms.createTenantDek(scope.tenantId);
80
68
  if (created?.key) return created.key;
81
- return deriveDekFromSecret(resolveFallbackEncryptionSecret(), scope.tenantId);
69
+ throw new CredentialsEncryptionUnavailableError();
82
70
  }
83
71
  async function encryptCredentialsBlob(credentials, scope) {
84
72
  const dek = await resolveCredentialsDek(scope);
@@ -124,8 +112,8 @@ function createCredentialsService(em) {
124
112
  return this.getRaw(definition.bundleId, scope);
125
113
  },
126
114
  async save(integrationId, credentials, scope) {
127
- await ensureCredentialsEncryptionMap(scope);
128
115
  const encryptedCredentials = await encryptCredentialsBlob(credentials, scope);
116
+ await ensureCredentialsEncryptionMap(scope);
129
117
  const row = await findOneWithDecryption(
130
118
  em,
131
119
  IntegrationCredentials,
@@ -169,6 +157,8 @@ function createCredentialsService(em) {
169
157
  };
170
158
  }
171
159
  export {
172
- createCredentialsService
160
+ CredentialsEncryptionUnavailableError,
161
+ createCredentialsService,
162
+ isCredentialsEncryptionUnavailableError
173
163
  };
174
164
  //# sourceMappingURL=credentials-service.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/integrations/lib/credentials-service.ts"],
4
- "sourcesContent": ["import crypto from 'node:crypto'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { decryptWithAesGcm, encryptWithAesGcm } from '@open-mercato/shared/lib/encryption/aes'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { createKmsService } from '@open-mercato/shared/lib/encryption/kms'\nimport { parseDecryptedFieldValue } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport {\n getBundle,\n getIntegration,\n resolveIntegrationCredentialsSchema,\n type IntegrationScope,\n} from '@open-mercato/shared/modules/integrations/types'\nimport { EncryptionMap } from '../../entities/data/entities'\nimport { IntegrationCredentials } from '../data/entities'\n\nconst ENCRYPTED_CREDENTIALS_BLOB_KEY = '__om_encrypted_credentials_blob_v1'\nconst DERIVED_KEY_CONTEXT = 'integrations.credentials'\n\nfunction isRecordValue(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === 'object' && !Array.isArray(value)\n}\n\nfunction normalizeCredentialsRecord(value: unknown): Record<string, unknown> {\n if (isRecordValue(value)) return value\n if (typeof value !== 'string') return {}\n\n const parsed = parseDecryptedFieldValue(value)\n return isRecordValue(parsed) ? parsed : {}\n}\n\nfunction resolveFallbackEncryptionSecret(): string {\n const candidates = [\n process.env.TENANT_DATA_ENCRYPTION_FALLBACK_KEY,\n process.env.TENANT_DATA_ENCRYPTION_KEY,\n process.env.AUTH_SECRET,\n process.env.NEXTAUTH_SECRET,\n ]\n\n for (const value of candidates) {\n const normalized = value?.trim()\n if (normalized) return normalized\n }\n\n if (process.env.NODE_ENV !== 'production') return 'om-dev-tenant-encryption'\n\n console.warn(\n '[integrations.credentials] No encryption secret configured; using emergency fallback secret. Configure TENANT_DATA_ENCRYPTION_FALLBACK_KEY immediately.',\n )\n return 'om-emergency-fallback-rotate-me'\n}\n\nfunction deriveDekFromSecret(secret: string, tenantId: string): string {\n return crypto\n .createHash('sha256')\n .update(`${DERIVED_KEY_CONTEXT}:${tenantId}:${secret}`)\n .digest()\n .toString('base64')\n}\n\nexport function createCredentialsService(em: EntityManager) {\n const credentialsEncryptionSpec = [{ field: 'credentials' }]\n\n async function ensureCredentialsEncryptionMap(scope: IntegrationScope): Promise<void> {\n const existing = await findOneWithDecryption(\n em,\n EncryptionMap,\n {\n entityId: 'integrations:integration_credentials',\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n\n if (!existing) {\n const created = em.create(EncryptionMap, {\n entityId: 'integrations:integration_credentials',\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n fieldsJson: credentialsEncryptionSpec,\n isActive: true,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(created)\n return\n }\n\n existing.fieldsJson = credentialsEncryptionSpec\n existing.isActive = true\n }\n\n async function resolveCredentialsDek(scope: IntegrationScope): Promise<string> {\n const kms = createKmsService()\n const existing = await kms.getTenantDek(scope.tenantId)\n if (existing?.key) return existing.key\n\n const created = await kms.createTenantDek(scope.tenantId)\n if (created?.key) return created.key\n\n return deriveDekFromSecret(resolveFallbackEncryptionSecret(), scope.tenantId)\n }\n\n async function encryptCredentialsBlob(\n credentials: Record<string, unknown>,\n scope: IntegrationScope,\n ): Promise<Record<string, unknown>> {\n const dek = await resolveCredentialsDek(scope)\n const payload = encryptWithAesGcm(JSON.stringify(credentials), dek)\n return { [ENCRYPTED_CREDENTIALS_BLOB_KEY]: payload.value }\n }\n\n async function decryptCredentialsBlob(\n credentialsInput: unknown,\n scope: IntegrationScope,\n ): Promise<Record<string, unknown>> {\n const credentials = normalizeCredentialsRecord(credentialsInput)\n const encrypted = credentials[ENCRYPTED_CREDENTIALS_BLOB_KEY]\n if (typeof encrypted !== 'string' || !encrypted) return credentials\n\n const dek = await resolveCredentialsDek(scope)\n const decryptedRaw = decryptWithAesGcm(encrypted, dek)\n if (!decryptedRaw) return {}\n\n try {\n const parsed = JSON.parse(decryptedRaw) as unknown\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : {}\n } catch {\n return {}\n }\n }\n\n return {\n async getRaw(integrationId: string, scope: IntegrationScope): Promise<Record<string, unknown> | null> {\n const row = await findOneWithDecryption(\n em,\n IntegrationCredentials,\n {\n integrationId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n if (!row) return null\n return decryptCredentialsBlob(row.credentials, scope)\n },\n\n async resolve(integrationId: string, scope: IntegrationScope): Promise<Record<string, unknown> | null> {\n const direct = await this.getRaw(integrationId, scope)\n if (direct) return direct\n\n const definition = getIntegration(integrationId)\n if (!definition?.bundleId) return null\n return this.getRaw(definition.bundleId, scope)\n },\n\n async save(integrationId: string, credentials: Record<string, unknown>, scope: IntegrationScope): Promise<void> {\n await ensureCredentialsEncryptionMap(scope)\n const encryptedCredentials = await encryptCredentialsBlob(credentials, scope)\n\n const row = await findOneWithDecryption(\n em,\n IntegrationCredentials,\n {\n integrationId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n\n if (row) {\n row.credentials = encryptedCredentials\n await em.flush()\n return\n }\n\n const created = em.create(IntegrationCredentials, {\n integrationId,\n credentials: encryptedCredentials,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n })\n await em.persist(created).flush()\n },\n\n async saveField(\n integrationId: string,\n fieldKey: string,\n value: unknown,\n scope: IntegrationScope,\n ): Promise<Record<string, unknown>> {\n const current = (await this.getRaw(integrationId, scope)) ?? {}\n const updated = { ...current, [fieldKey]: value }\n await this.save(integrationId, updated, scope)\n return updated\n },\n\n getSchema(integrationId: string) {\n const definition = getIntegration(integrationId)\n if (!definition) return undefined\n\n if (definition.bundleId) {\n const bundle = getBundle(definition.bundleId)\n return bundle?.credentials ?? resolveIntegrationCredentialsSchema(integrationId)\n }\n\n return definition.credentials ?? resolveIntegrationCredentialsSchema(integrationId)\n },\n }\n}\n\nexport type CredentialsService = ReturnType<typeof createCredentialsService>\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { decryptWithAesGcm, encryptWithAesGcm } from '@open-mercato/shared/lib/encryption/aes'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { createKmsService } from '@open-mercato/shared/lib/encryption/kms'\nimport { parseDecryptedFieldValue } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport {\n getBundle,\n getIntegration,\n resolveIntegrationCredentialsSchema,\n type IntegrationScope,\n} from '@open-mercato/shared/modules/integrations/types'\nimport { EncryptionMap } from '../../entities/data/entities'\nimport { IntegrationCredentials } from '../data/entities'\n\nconst ENCRYPTED_CREDENTIALS_BLOB_KEY = '__om_encrypted_credentials_blob_v1'\nconst CREDENTIALS_ENCRYPTION_UNAVAILABLE_MESSAGE =\n 'Integration credentials encryption key is unavailable. Configure Vault KMS or TENANT_DATA_ENCRYPTION_FALLBACK_KEY.'\n\nexport class CredentialsEncryptionUnavailableError extends Error {\n constructor() {\n super(CREDENTIALS_ENCRYPTION_UNAVAILABLE_MESSAGE)\n this.name = 'CredentialsEncryptionUnavailableError'\n }\n}\n\nexport function isCredentialsEncryptionUnavailableError(error: unknown): error is CredentialsEncryptionUnavailableError {\n return error instanceof CredentialsEncryptionUnavailableError\n}\n\nfunction isRecordValue(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === 'object' && !Array.isArray(value)\n}\n\nfunction normalizeCredentialsRecord(value: unknown): Record<string, unknown> {\n if (isRecordValue(value)) return value\n if (typeof value !== 'string') return {}\n\n const parsed = parseDecryptedFieldValue(value)\n return isRecordValue(parsed) ? parsed : {}\n}\n\nexport function createCredentialsService(em: EntityManager) {\n const credentialsEncryptionSpec = [{ field: 'credentials' }]\n\n async function ensureCredentialsEncryptionMap(scope: IntegrationScope): Promise<void> {\n const existing = await findOneWithDecryption(\n em,\n EncryptionMap,\n {\n entityId: 'integrations:integration_credentials',\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n\n if (!existing) {\n const created = em.create(EncryptionMap, {\n entityId: 'integrations:integration_credentials',\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n fieldsJson: credentialsEncryptionSpec,\n isActive: true,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(created)\n return\n }\n\n existing.fieldsJson = credentialsEncryptionSpec\n existing.isActive = true\n }\n\n async function resolveCredentialsDek(scope: IntegrationScope): Promise<string> {\n const kms = createKmsService()\n const existing = await kms.getTenantDek(scope.tenantId)\n if (existing?.key) return existing.key\n\n const created = await kms.createTenantDek(scope.tenantId)\n if (created?.key) return created.key\n\n throw new CredentialsEncryptionUnavailableError()\n }\n\n async function encryptCredentialsBlob(\n credentials: Record<string, unknown>,\n scope: IntegrationScope,\n ): Promise<Record<string, unknown>> {\n const dek = await resolveCredentialsDek(scope)\n const payload = encryptWithAesGcm(JSON.stringify(credentials), dek)\n return { [ENCRYPTED_CREDENTIALS_BLOB_KEY]: payload.value }\n }\n\n async function decryptCredentialsBlob(\n credentialsInput: unknown,\n scope: IntegrationScope,\n ): Promise<Record<string, unknown>> {\n const credentials = normalizeCredentialsRecord(credentialsInput)\n const encrypted = credentials[ENCRYPTED_CREDENTIALS_BLOB_KEY]\n if (typeof encrypted !== 'string' || !encrypted) return credentials\n\n const dek = await resolveCredentialsDek(scope)\n const decryptedRaw = decryptWithAesGcm(encrypted, dek)\n if (!decryptedRaw) return {}\n\n try {\n const parsed = JSON.parse(decryptedRaw) as unknown\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : {}\n } catch {\n return {}\n }\n }\n\n return {\n async getRaw(integrationId: string, scope: IntegrationScope): Promise<Record<string, unknown> | null> {\n const row = await findOneWithDecryption(\n em,\n IntegrationCredentials,\n {\n integrationId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n if (!row) return null\n return decryptCredentialsBlob(row.credentials, scope)\n },\n\n async resolve(integrationId: string, scope: IntegrationScope): Promise<Record<string, unknown> | null> {\n const direct = await this.getRaw(integrationId, scope)\n if (direct) return direct\n\n const definition = getIntegration(integrationId)\n if (!definition?.bundleId) return null\n return this.getRaw(definition.bundleId, scope)\n },\n\n async save(integrationId: string, credentials: Record<string, unknown>, scope: IntegrationScope): Promise<void> {\n const encryptedCredentials = await encryptCredentialsBlob(credentials, scope)\n await ensureCredentialsEncryptionMap(scope)\n\n const row = await findOneWithDecryption(\n em,\n IntegrationCredentials,\n {\n integrationId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n deletedAt: null,\n },\n undefined,\n scope,\n )\n\n if (row) {\n row.credentials = encryptedCredentials\n await em.flush()\n return\n }\n\n const created = em.create(IntegrationCredentials, {\n integrationId,\n credentials: encryptedCredentials,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n })\n await em.persist(created).flush()\n },\n\n async saveField(\n integrationId: string,\n fieldKey: string,\n value: unknown,\n scope: IntegrationScope,\n ): Promise<Record<string, unknown>> {\n const current = (await this.getRaw(integrationId, scope)) ?? {}\n const updated = { ...current, [fieldKey]: value }\n await this.save(integrationId, updated, scope)\n return updated\n },\n\n getSchema(integrationId: string) {\n const definition = getIntegration(integrationId)\n if (!definition) return undefined\n\n if (definition.bundleId) {\n const bundle = getBundle(definition.bundleId)\n return bundle?.credentials ?? resolveIntegrationCredentialsSchema(integrationId)\n }\n\n return definition.credentials ?? resolveIntegrationCredentialsSchema(integrationId)\n },\n }\n}\n\nexport type CredentialsService = ReturnType<typeof createCredentialsService>\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,17 @@
1
1
  const features = [
2
2
  { id: "query_index.status.view", title: "View index status", module: "query_index" },
3
- { id: "query_index.reindex", title: "Trigger reindex", module: "query_index" },
4
- { id: "query_index.purge", title: "Purge index", module: "query_index" }
3
+ {
4
+ id: "query_index.reindex",
5
+ title: "Trigger reindex",
6
+ module: "query_index",
7
+ dependsOn: ["query_index.status.view"]
8
+ },
9
+ {
10
+ id: "query_index.purge",
11
+ title: "Purge index",
12
+ module: "query_index",
13
+ dependsOn: ["query_index.status.view"]
14
+ }
5
15
  ];
6
16
  var acl_default = features;
7
17
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/query_index/acl.ts"],
4
- "sourcesContent": ["export const features = [\n { id: 'query_index.status.view', title: 'View index status', module: 'query_index' },\n { id: 'query_index.reindex', title: 'Trigger reindex', module: 'query_index' },\n { id: 'query_index.purge', title: 'Purge index', module: 'query_index' },\n]\n\nexport default features\n"],
5
- "mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,EAAE,IAAI,2BAA2B,OAAO,qBAAqB,QAAQ,cAAc;AAAA,EACnF,EAAE,IAAI,uBAAuB,OAAO,mBAAmB,QAAQ,cAAc;AAAA,EAC7E,EAAE,IAAI,qBAAqB,OAAO,eAAe,QAAQ,cAAc;AACzE;AAEA,IAAO,cAAQ;",
4
+ "sourcesContent": ["export const features = [\n { id: 'query_index.status.view', title: 'View index status', module: 'query_index' },\n {\n id: 'query_index.reindex',\n title: 'Trigger reindex',\n module: 'query_index',\n dependsOn: ['query_index.status.view'],\n },\n {\n id: 'query_index.purge',\n title: 'Purge index',\n module: 'query_index',\n dependsOn: ['query_index.status.view'],\n },\n]\n\nexport default features\n"],
5
+ "mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,EAAE,IAAI,2BAA2B,OAAO,qBAAqB,QAAQ,cAAc;AAAA,EACnF;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,yBAAyB;AAAA,EACvC;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,yBAAyB;AAAA,EACvC;AACF;AAEA,IAAO,cAAQ;",
6
6
  "names": []
7
7
  }