@open-mercato/core 0.6.6-develop.6290.1.4bb5a8ba3f → 0.6.6-develop.6299.1.29224e2d86

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 (37) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +8 -0
  3. package/dist/generated/entities/module_config/index.js +4 -0
  4. package/dist/generated/entities/module_config/index.js.map +2 -2
  5. package/dist/generated/entity-fields-registry.js +2 -0
  6. package/dist/generated/entity-fields-registry.js.map +2 -2
  7. package/dist/modules/auth/lib/setup-app.js +1 -1
  8. package/dist/modules/auth/lib/setup-app.js.map +2 -2
  9. package/dist/modules/catalog/components/products/ProductComplianceSection.js +235 -62
  10. package/dist/modules/catalog/components/products/ProductComplianceSection.js.map +2 -2
  11. package/dist/modules/configs/components/CachePanel.js +2 -1
  12. package/dist/modules/configs/components/CachePanel.js.map +2 -2
  13. package/dist/modules/configs/components/SystemStatusPanel.js +10 -12
  14. package/dist/modules/configs/components/SystemStatusPanel.js.map +2 -2
  15. package/dist/modules/configs/data/entities.js +15 -1
  16. package/dist/modules/configs/data/entities.js.map +2 -2
  17. package/dist/modules/configs/lib/module-config-service.js +52 -19
  18. package/dist/modules/configs/lib/module-config-service.js.map +3 -3
  19. package/dist/modules/configs/migrations/Migration20260617150000.js +21 -0
  20. package/dist/modules/configs/migrations/Migration20260617150000.js.map +7 -0
  21. package/dist/modules/customers/api/activities/route.js.map +2 -2
  22. package/dist/modules/inbox_ops/api/proposals/[id]/replies/[replyId]/send/route.js +47 -1
  23. package/dist/modules/inbox_ops/api/proposals/[id]/replies/[replyId]/send/route.js.map +2 -2
  24. package/generated/entities/module_config/index.ts +2 -0
  25. package/generated/entity-fields-registry.ts +2 -0
  26. package/package.json +7 -7
  27. package/src/modules/auth/lib/setup-app.ts +1 -1
  28. package/src/modules/catalog/components/products/ProductComplianceSection.tsx +138 -27
  29. package/src/modules/configs/components/CachePanel.tsx +2 -3
  30. package/src/modules/configs/components/SystemStatusPanel.tsx +12 -19
  31. package/src/modules/configs/data/entities.ts +17 -1
  32. package/src/modules/configs/lib/module-config-service.ts +83 -24
  33. package/src/modules/configs/migrations/.snapshot-open-mercato.json +41 -4
  34. package/src/modules/configs/migrations/.snapshot-openmercato.json +41 -4
  35. package/src/modules/configs/migrations/Migration20260617150000.ts +21 -0
  36. package/src/modules/customers/api/activities/route.ts +6 -0
  37. package/src/modules/inbox_ops/api/proposals/[id]/replies/[replyId]/send/route.ts +54 -1
@@ -1,8 +1,9 @@
1
1
  import { ModuleConfig } from "../data/entities.js";
2
2
  import { moduleConfigKeySchema } from "../data/validators.js";
3
- const CACHE_VERSION = "v1";
3
+ const CACHE_VERSION = "v2";
4
4
  const CACHE_TTL_MS = 6e4;
5
- const cacheKey = (moduleId, name) => `module-config:${CACHE_VERSION}:${moduleId}:${name}`;
5
+ const scopeKeyPart = (tenantId) => tenantId ?? "global";
6
+ const cacheKey = (moduleId, name, tenantId) => `module-config:${CACHE_VERSION}:${moduleId}:${name}:${scopeKeyPart(tenantId)}`;
6
7
  const moduleTag = (moduleId) => `module-config:module:${moduleId}`;
7
8
  const resolveEm = (container) => {
8
9
  try {
@@ -18,10 +19,13 @@ const resolveCache = (container) => {
18
19
  return null;
19
20
  }
20
21
  };
21
- const toRecord = (entity) => ({
22
+ const toRecord = (entity, source) => ({
22
23
  moduleId: entity.moduleId,
23
24
  name: entity.name,
24
25
  value: entity.valueJson ?? null,
26
+ tenantId: entity.tenantId ?? null,
27
+ organizationId: entity.organizationId ?? null,
28
+ source,
25
29
  createdAt: entity.createdAt.toISOString(),
26
30
  updatedAt: entity.updatedAt.toISOString()
27
31
  });
@@ -59,10 +63,11 @@ const deleteCacheByModule = async (cache, moduleId) => {
59
63
  };
60
64
  const normalizeKey = (moduleId, name) => moduleConfigKeySchema.parse({ moduleId, name });
61
65
  function createModuleConfigService(container) {
62
- const getRecord = async (rawModuleId, rawName) => {
66
+ const getRecord = async (rawModuleId, rawName, scope) => {
63
67
  const { moduleId, name } = normalizeKey(rawModuleId, rawName);
68
+ const tenantId = scope?.tenantId ?? null;
64
69
  const cache = resolveCache(container);
65
- const key = cacheKey(moduleId, name);
70
+ const key = cacheKey(moduleId, name, tenantId);
66
71
  const cached = await readCache(cache, key);
67
72
  if (cached) {
68
73
  if (!cached.found) return null;
@@ -71,45 +76,71 @@ function createModuleConfigService(container) {
71
76
  const em = resolveEm(container);
72
77
  if (!em) return null;
73
78
  const repo = em.getRepository(ModuleConfig);
74
- const entity = await repo.findOne({ moduleId, name });
75
- if (!entity) {
79
+ if (tenantId) {
80
+ const scoped = await repo.findOne({ moduleId, name, tenantId });
81
+ if (scoped) {
82
+ const record3 = toRecord(scoped, "tenant");
83
+ await writeCache(cache, key, { found: true, record: record3 }, moduleId);
84
+ return record3;
85
+ }
86
+ const global2 = await repo.findOne({ moduleId, name, tenantId: null });
87
+ if (!global2) {
88
+ await writeCache(cache, key, { found: false }, moduleId);
89
+ return null;
90
+ }
91
+ const record2 = toRecord(global2, "instance");
92
+ await writeCache(cache, key, { found: true, record: record2 }, moduleId);
93
+ return record2;
94
+ }
95
+ const global = await repo.findOne({ moduleId, name, tenantId: null });
96
+ if (!global) {
76
97
  await writeCache(cache, key, { found: false }, moduleId);
77
98
  return null;
78
99
  }
79
- const record = toRecord(entity);
100
+ const record = toRecord(global, "instance");
80
101
  await writeCache(cache, key, { found: true, record }, moduleId);
81
102
  return record;
82
103
  };
83
104
  const getValue = async (rawModuleId, rawName, options) => {
84
- const record = await getRecord(rawModuleId, rawName);
105
+ const record = await getRecord(rawModuleId, rawName, options?.scope);
85
106
  if (!record) return options?.defaultValue ?? null;
86
107
  const value = record.value;
87
108
  if (value === void 0 || value === null) return options?.defaultValue ?? null;
88
109
  return value;
89
110
  };
90
- const setValue = async (rawModuleId, rawName, value) => {
111
+ const setValue = async (rawModuleId, rawName, value, scope) => {
91
112
  const em = resolveEm(container);
92
113
  if (!em) return null;
93
114
  const { moduleId, name } = normalizeKey(rawModuleId, rawName);
115
+ const tenantId = scope?.tenantId ?? null;
116
+ const organizationId = scope?.organizationId ?? null;
94
117
  const repo = em.getRepository(ModuleConfig);
95
118
  const now = /* @__PURE__ */ new Date();
96
- let entity = await repo.findOne({ moduleId, name });
119
+ let entity = await repo.findOne({ moduleId, name, tenantId });
97
120
  if (!entity) {
98
121
  entity = repo.create({
99
122
  moduleId,
100
123
  name,
101
124
  valueJson: value ?? null,
125
+ tenantId,
126
+ organizationId,
102
127
  createdAt: now,
103
128
  updatedAt: now
104
129
  });
105
130
  em.persist(entity);
106
131
  } else {
107
132
  entity.valueJson = value ?? null;
133
+ entity.organizationId = organizationId;
108
134
  entity.updatedAt = now;
109
135
  }
110
136
  await em.flush();
111
- const record = toRecord(entity);
112
- await writeCache(resolveCache(container), cacheKey(moduleId, name), { found: true, record }, moduleId);
137
+ const record = toRecord(entity, tenantId ? "tenant" : "instance");
138
+ const cache = resolveCache(container);
139
+ if (tenantId) {
140
+ await writeCache(cache, cacheKey(moduleId, name, tenantId), { found: true, record }, moduleId);
141
+ } else {
142
+ await deleteCacheByModule(cache, moduleId);
143
+ }
113
144
  return record;
114
145
  };
115
146
  const restoreDefaults = async (defaults, options) => {
@@ -124,12 +155,14 @@ function createModuleConfigService(container) {
124
155
  let entity = null;
125
156
  try {
126
157
  const { moduleId, name } = normalizeKey(entry.moduleId, entry.name);
127
- entity = await repo.findOne({ moduleId, name });
158
+ entity = await repo.findOne({ moduleId, name, tenantId: null });
128
159
  if (!entity) {
129
160
  entity = repo.create({
130
161
  moduleId,
131
162
  name,
132
- valueJson: entry.value ?? null
163
+ valueJson: entry.value ?? null,
164
+ tenantId: null,
165
+ organizationId: null
133
166
  });
134
167
  em.persist(entity);
135
168
  dirty = true;
@@ -146,16 +179,16 @@ function createModuleConfigService(container) {
146
179
  if (!dirty) return;
147
180
  await em.flush();
148
181
  for (const entity of touched) {
149
- const record = toRecord(entity);
150
- await writeCache(cache, cacheKey(entity.moduleId, entity.name), { found: true, record }, entity.moduleId);
182
+ const record = toRecord(entity, "instance");
183
+ await writeCache(cache, cacheKey(entity.moduleId, entity.name, null), { found: true, record }, entity.moduleId);
151
184
  }
152
185
  };
153
- const invalidate = async (rawModuleId, rawName) => {
186
+ const invalidate = async (rawModuleId, rawName, scope) => {
154
187
  const cache = resolveCache(container);
155
188
  if (!cache) return;
156
189
  if (rawName) {
157
190
  const { moduleId: moduleId2, name } = normalizeKey(rawModuleId, rawName);
158
- await deleteCacheKey(cache, cacheKey(moduleId2, name));
191
+ await deleteCacheKey(cache, cacheKey(moduleId2, name, scope?.tenantId ?? null));
159
192
  return;
160
193
  }
161
194
  const moduleId = moduleConfigKeySchema.shape.moduleId.parse(rawModuleId);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/configs/lib/module-config-service.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { CacheStrategy } from '@open-mercato/cache'\nimport type { AppContainer } from '@open-mercato/shared/lib/di/container'\nimport { ModuleConfig } from '../data/entities'\nimport { moduleConfigKeySchema } from '../data/validators'\n\nconst CACHE_VERSION = 'v1'\nconst CACHE_TTL_MS = 60_000\n\ntype CachePayload = {\n found: boolean\n record?: ModuleConfigRecord | null\n}\n\nconst cacheKey = (moduleId: string, name: string) => `module-config:${CACHE_VERSION}:${moduleId}:${name}`\nconst moduleTag = (moduleId: string) => `module-config:module:${moduleId}`\n\nconst resolveEm = (container: AppContainer): EntityManager | null => {\n try {\n return (container.resolve('em') as EntityManager)\n } catch {\n return null\n }\n}\n\nconst resolveCache = (container: AppContainer): CacheStrategy | null => {\n try {\n return (container.resolve('cache') as CacheStrategy)\n } catch {\n return null\n }\n}\n\nconst toRecord = (entity: ModuleConfig): ModuleConfigRecord => ({\n moduleId: entity.moduleId,\n name: entity.name,\n value: entity.valueJson ?? null,\n createdAt: entity.createdAt.toISOString(),\n updatedAt: entity.updatedAt.toISOString(),\n})\n\nconst readCache = async (cache: CacheStrategy | null, key: string): Promise<CachePayload | null> => {\n if (!cache) return null\n try {\n const cached = await cache.get(key)\n if (cached && typeof cached === 'object' && 'found' in cached) {\n return cached as CachePayload\n }\n } catch {}\n return null\n}\n\nconst writeCache = async (cache: CacheStrategy | null, key: string, payload: CachePayload, moduleId: string) => {\n if (!cache) return\n try {\n await cache.set(key, payload, { ttl: CACHE_TTL_MS, tags: [moduleTag(moduleId)] })\n } catch {}\n}\n\nconst deleteCacheKey = async (cache: CacheStrategy | null, key: string) => {\n if (!cache) return\n try {\n await cache.delete(key)\n } catch {}\n}\n\nconst deleteCacheByModule = async (cache: CacheStrategy | null, moduleId: string) => {\n if (!cache) return\n try {\n await cache.deleteByTags([moduleTag(moduleId)])\n } catch {}\n}\n\nconst normalizeKey = (moduleId: string, name: string) => moduleConfigKeySchema.parse({ moduleId, name })\n\nexport type ModuleConfigRecord = {\n moduleId: string\n name: string\n value: unknown\n createdAt: string\n updatedAt: string\n}\n\nexport type ModuleConfigDefault = {\n moduleId: string\n name: string\n value: unknown\n}\n\nexport type ModuleConfigService = {\n getRecord(moduleId: string, name: string): Promise<ModuleConfigRecord | null>\n getValue<T = unknown>(moduleId: string, name: string, options?: { defaultValue?: T | null }): Promise<T | null>\n setValue(moduleId: string, name: string, value: unknown): Promise<ModuleConfigRecord | null>\n restoreDefaults(defaults: ModuleConfigDefault[], options?: { force?: boolean }): Promise<void>\n invalidate(moduleId: string, name?: string): Promise<void>\n}\n\nexport function createModuleConfigService(container: AppContainer): ModuleConfigService {\n const getRecord = async (rawModuleId: string, rawName: string): Promise<ModuleConfigRecord | null> => {\n const { moduleId, name } = normalizeKey(rawModuleId, rawName)\n const cache = resolveCache(container)\n const key = cacheKey(moduleId, name)\n const cached = await readCache(cache, key)\n if (cached) {\n if (!cached.found) return null\n if (cached.record) return cached.record\n }\n\n const em = resolveEm(container)\n if (!em) return null\n const repo = em.getRepository(ModuleConfig)\n const entity = await repo.findOne({ moduleId, name })\n if (!entity) {\n await writeCache(cache, key, { found: false }, moduleId)\n return null\n }\n const record = toRecord(entity)\n await writeCache(cache, key, { found: true, record }, moduleId)\n return record\n }\n\n const getValue = async <T>(rawModuleId: string, rawName: string, options?: { defaultValue?: T | null }): Promise<T | null> => {\n const record = await getRecord(rawModuleId, rawName)\n if (!record) return options?.defaultValue ?? null\n const value = record.value as T | null | undefined\n if (value === undefined || value === null) return options?.defaultValue ?? null\n return value\n }\n\n const setValue = async (rawModuleId: string, rawName: string, value: unknown): Promise<ModuleConfigRecord | null> => {\n const em = resolveEm(container)\n if (!em) return null\n const { moduleId, name } = normalizeKey(rawModuleId, rawName)\n const repo = em.getRepository(ModuleConfig)\n const now = new Date()\n let entity = await repo.findOne({ moduleId, name })\n if (!entity) {\n entity = repo.create({\n moduleId,\n name,\n valueJson: value ?? null,\n createdAt: now,\n updatedAt: now,\n })\n em.persist(entity)\n } else {\n entity.valueJson = value ?? null\n entity.updatedAt = now\n }\n await em.flush()\n const record = toRecord(entity)\n await writeCache(resolveCache(container), cacheKey(moduleId, name), { found: true, record }, moduleId)\n return record\n }\n\n const restoreDefaults = async (defaults: ModuleConfigDefault[], options?: { force?: boolean }) => {\n if (!Array.isArray(defaults) || defaults.length === 0) return\n const em = resolveEm(container)\n if (!em) return\n const repo = em.getRepository(ModuleConfig)\n const cache = resolveCache(container)\n let dirty = false\n const touched: ModuleConfig[] = []\n for (const entry of defaults) {\n let entity: ModuleConfig | null = null\n try {\n const { moduleId, name } = normalizeKey(entry.moduleId, entry.name)\n entity = await repo.findOne({ moduleId, name })\n if (!entity) {\n entity = repo.create({\n moduleId,\n name,\n valueJson: entry.value ?? null,\n })\n em.persist(entity)\n dirty = true\n touched.push(entity)\n } else if (options?.force) {\n entity.valueJson = entry.value ?? null\n entity.updatedAt = new Date()\n dirty = true\n touched.push(entity)\n }\n } catch {}\n }\n if (!dirty) return\n await em.flush()\n for (const entity of touched) {\n const record = toRecord(entity)\n await writeCache(cache, cacheKey(entity.moduleId, entity.name), { found: true, record }, entity.moduleId)\n }\n }\n\n const invalidate = async (rawModuleId: string, rawName?: string) => {\n const cache = resolveCache(container)\n if (!cache) return\n if (rawName) {\n const { moduleId, name } = normalizeKey(rawModuleId, rawName)\n await deleteCacheKey(cache, cacheKey(moduleId, name))\n return\n }\n const moduleId = moduleConfigKeySchema.shape.moduleId.parse(rawModuleId)\n await deleteCacheByModule(cache, moduleId)\n }\n\n return {\n getRecord,\n getValue,\n setValue,\n restoreDefaults,\n invalidate,\n }\n}\n\n"],
5
- "mappings": "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",
6
- "names": ["moduleId"]
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { CacheStrategy } from '@open-mercato/cache'\nimport type { AppContainer } from '@open-mercato/shared/lib/di/container'\nimport { ModuleConfig } from '../data/entities'\nimport { moduleConfigKeySchema } from '../data/validators'\n\nconst CACHE_VERSION = 'v2'\nconst CACHE_TTL_MS = 60_000\n\ntype CachePayload = {\n found: boolean\n record?: ModuleConfigRecord | null\n}\n\nconst scopeKeyPart = (tenantId?: string | null) => (tenantId ?? 'global')\n\nconst cacheKey = (moduleId: string, name: string, tenantId?: string | null) =>\n `module-config:${CACHE_VERSION}:${moduleId}:${name}:${scopeKeyPart(tenantId)}`\nconst moduleTag = (moduleId: string) => `module-config:module:${moduleId}`\n\nconst resolveEm = (container: AppContainer): EntityManager | null => {\n try {\n return (container.resolve('em') as EntityManager)\n } catch {\n return null\n }\n}\n\nconst resolveCache = (container: AppContainer): CacheStrategy | null => {\n try {\n return (container.resolve('cache') as CacheStrategy)\n } catch {\n return null\n }\n}\n\nconst toRecord = (entity: ModuleConfig, source: ModuleConfigSource): ModuleConfigRecord => ({\n moduleId: entity.moduleId,\n name: entity.name,\n value: entity.valueJson ?? null,\n tenantId: entity.tenantId ?? null,\n organizationId: entity.organizationId ?? null,\n source,\n createdAt: entity.createdAt.toISOString(),\n updatedAt: entity.updatedAt.toISOString(),\n})\n\nconst readCache = async (cache: CacheStrategy | null, key: string): Promise<CachePayload | null> => {\n if (!cache) return null\n try {\n const cached = await cache.get(key)\n if (cached && typeof cached === 'object' && 'found' in cached) {\n return cached as CachePayload\n }\n } catch {}\n return null\n}\n\nconst writeCache = async (cache: CacheStrategy | null, key: string, payload: CachePayload, moduleId: string) => {\n if (!cache) return\n try {\n await cache.set(key, payload, { ttl: CACHE_TTL_MS, tags: [moduleTag(moduleId)] })\n } catch {}\n}\n\nconst deleteCacheKey = async (cache: CacheStrategy | null, key: string) => {\n if (!cache) return\n try {\n await cache.delete(key)\n } catch {}\n}\n\nconst deleteCacheByModule = async (cache: CacheStrategy | null, moduleId: string) => {\n if (!cache) return\n try {\n await cache.deleteByTags([moduleTag(moduleId)])\n } catch {}\n}\n\nconst normalizeKey = (moduleId: string, name: string) => moduleConfigKeySchema.parse({ moduleId, name })\n\nexport type ModuleConfigSource = 'tenant' | 'instance'\n\nexport type ConfigScope = {\n tenantId?: string | null\n organizationId?: string | null\n}\n\nexport type ModuleConfigRecord = {\n moduleId: string\n name: string\n value: unknown\n tenantId: string | null\n organizationId: string | null\n source: ModuleConfigSource\n createdAt: string\n updatedAt: string\n}\n\nexport type ModuleConfigDefault = {\n moduleId: string\n name: string\n value: unknown\n}\n\nexport type ModuleConfigService = {\n getRecord(moduleId: string, name: string, scope?: ConfigScope): Promise<ModuleConfigRecord | null>\n getValue<T = unknown>(\n moduleId: string,\n name: string,\n options?: { defaultValue?: T | null; scope?: ConfigScope },\n ): Promise<T | null>\n setValue(moduleId: string, name: string, value: unknown, scope?: ConfigScope): Promise<ModuleConfigRecord | null>\n restoreDefaults(defaults: ModuleConfigDefault[], options?: { force?: boolean }): Promise<void>\n invalidate(moduleId: string, name?: string, scope?: ConfigScope): Promise<void>\n}\n\nexport function createModuleConfigService(container: AppContainer): ModuleConfigService {\n const getRecord = async (rawModuleId: string, rawName: string, scope?: ConfigScope): Promise<ModuleConfigRecord | null> => {\n const { moduleId, name } = normalizeKey(rawModuleId, rawName)\n const tenantId = scope?.tenantId ?? null\n const cache = resolveCache(container)\n const key = cacheKey(moduleId, name, tenantId)\n const cached = await readCache(cache, key)\n if (cached) {\n if (!cached.found) return null\n if (cached.record) return cached.record\n }\n\n const em = resolveEm(container)\n if (!em) return null\n const repo = em.getRepository(ModuleConfig)\n\n if (tenantId) {\n const scoped = await repo.findOne({ moduleId, name, tenantId })\n if (scoped) {\n const record = toRecord(scoped, 'tenant')\n await writeCache(cache, key, { found: true, record }, moduleId)\n return record\n }\n const global = await repo.findOne({ moduleId, name, tenantId: null })\n if (!global) {\n await writeCache(cache, key, { found: false }, moduleId)\n return null\n }\n const record = toRecord(global, 'instance')\n await writeCache(cache, key, { found: true, record }, moduleId)\n return record\n }\n\n const global = await repo.findOne({ moduleId, name, tenantId: null })\n if (!global) {\n await writeCache(cache, key, { found: false }, moduleId)\n return null\n }\n const record = toRecord(global, 'instance')\n await writeCache(cache, key, { found: true, record }, moduleId)\n return record\n }\n\n const getValue = async <T>(\n rawModuleId: string,\n rawName: string,\n options?: { defaultValue?: T | null; scope?: ConfigScope },\n ): Promise<T | null> => {\n const record = await getRecord(rawModuleId, rawName, options?.scope)\n if (!record) return options?.defaultValue ?? null\n const value = record.value as T | null | undefined\n if (value === undefined || value === null) return options?.defaultValue ?? null\n return value\n }\n\n const setValue = async (\n rawModuleId: string,\n rawName: string,\n value: unknown,\n scope?: ConfigScope,\n ): Promise<ModuleConfigRecord | null> => {\n const em = resolveEm(container)\n if (!em) return null\n const { moduleId, name } = normalizeKey(rawModuleId, rawName)\n const tenantId = scope?.tenantId ?? null\n const organizationId = scope?.organizationId ?? null\n const repo = em.getRepository(ModuleConfig)\n const now = new Date()\n let entity = await repo.findOne({ moduleId, name, tenantId })\n if (!entity) {\n entity = repo.create({\n moduleId,\n name,\n valueJson: value ?? null,\n tenantId,\n organizationId,\n createdAt: now,\n updatedAt: now,\n })\n em.persist(entity)\n } else {\n entity.valueJson = value ?? null\n entity.organizationId = organizationId\n entity.updatedAt = now\n }\n await em.flush()\n const record = toRecord(entity, tenantId ? 'tenant' : 'instance')\n const cache = resolveCache(container)\n if (tenantId) {\n await writeCache(cache, cacheKey(moduleId, name, tenantId), { found: true, record }, moduleId)\n } else {\n await deleteCacheByModule(cache, moduleId)\n }\n return record\n }\n\n const restoreDefaults = async (defaults: ModuleConfigDefault[], options?: { force?: boolean }) => {\n if (!Array.isArray(defaults) || defaults.length === 0) return\n const em = resolveEm(container)\n if (!em) return\n const repo = em.getRepository(ModuleConfig)\n const cache = resolveCache(container)\n let dirty = false\n const touched: ModuleConfig[] = []\n for (const entry of defaults) {\n let entity: ModuleConfig | null = null\n try {\n const { moduleId, name } = normalizeKey(entry.moduleId, entry.name)\n entity = await repo.findOne({ moduleId, name, tenantId: null })\n if (!entity) {\n entity = repo.create({\n moduleId,\n name,\n valueJson: entry.value ?? null,\n tenantId: null,\n organizationId: null,\n })\n em.persist(entity)\n dirty = true\n touched.push(entity)\n } else if (options?.force) {\n entity.valueJson = entry.value ?? null\n entity.updatedAt = new Date()\n dirty = true\n touched.push(entity)\n }\n } catch {}\n }\n if (!dirty) return\n await em.flush()\n for (const entity of touched) {\n const record = toRecord(entity, 'instance')\n await writeCache(cache, cacheKey(entity.moduleId, entity.name, null), { found: true, record }, entity.moduleId)\n }\n }\n\n const invalidate = async (rawModuleId: string, rawName?: string, scope?: ConfigScope) => {\n const cache = resolveCache(container)\n if (!cache) return\n if (rawName) {\n const { moduleId, name } = normalizeKey(rawModuleId, rawName)\n await deleteCacheKey(cache, cacheKey(moduleId, name, scope?.tenantId ?? null))\n return\n }\n const moduleId = moduleConfigKeySchema.shape.moduleId.parse(rawModuleId)\n await deleteCacheByModule(cache, moduleId)\n }\n\n return {\n getRecord,\n getValue,\n setValue,\n restoreDefaults,\n invalidate,\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["record", "global", "moduleId"]
7
7
  }
@@ -0,0 +1,21 @@
1
+ import { Migration } from "@mikro-orm/migrations";
2
+ class Migration20260617150000 extends Migration {
3
+ async up() {
4
+ this.addSql(`alter table "module_configs" add column "organization_id" uuid null, add column "tenant_id" uuid null;`);
5
+ this.addSql(`alter table "module_configs" drop constraint "module_configs_module_name_unique";`);
6
+ this.addSql(`create unique index "module_configs_global_unique" on "module_configs" ("module_id", "name") where "tenant_id" is null;`);
7
+ this.addSql(`create unique index "module_configs_scoped_unique" on "module_configs" ("module_id", "name", "tenant_id") where "tenant_id" is not null;`);
8
+ this.addSql(`create index "module_configs_module_name_tenant_idx" on "module_configs" ("module_id", "name", "tenant_id");`);
9
+ }
10
+ async down() {
11
+ this.addSql(`drop index if exists "module_configs_module_name_tenant_idx";`);
12
+ this.addSql(`drop index if exists "module_configs_scoped_unique";`);
13
+ this.addSql(`drop index if exists "module_configs_global_unique";`);
14
+ this.addSql(`alter table "module_configs" add constraint "module_configs_module_name_unique" unique ("module_id", "name");`);
15
+ this.addSql(`alter table "module_configs" drop column "organization_id", drop column "tenant_id";`);
16
+ }
17
+ }
18
+ export {
19
+ Migration20260617150000
20
+ };
21
+ //# sourceMappingURL=Migration20260617150000.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/configs/migrations/Migration20260617150000.ts"],
4
+ "sourcesContent": ["import { Migration } from '@mikro-orm/migrations';\n\nexport class Migration20260617150000 extends Migration {\n\n override async up(): Promise<void> {\n this.addSql(`alter table \"module_configs\" add column \"organization_id\" uuid null, add column \"tenant_id\" uuid null;`);\n this.addSql(`alter table \"module_configs\" drop constraint \"module_configs_module_name_unique\";`);\n this.addSql(`create unique index \"module_configs_global_unique\" on \"module_configs\" (\"module_id\", \"name\") where \"tenant_id\" is null;`);\n this.addSql(`create unique index \"module_configs_scoped_unique\" on \"module_configs\" (\"module_id\", \"name\", \"tenant_id\") where \"tenant_id\" is not null;`);\n this.addSql(`create index \"module_configs_module_name_tenant_idx\" on \"module_configs\" (\"module_id\", \"name\", \"tenant_id\");`);\n }\n\n override async down(): Promise<void> {\n this.addSql(`drop index if exists \"module_configs_module_name_tenant_idx\";`);\n this.addSql(`drop index if exists \"module_configs_scoped_unique\";`);\n this.addSql(`drop index if exists \"module_configs_global_unique\";`);\n this.addSql(`alter table \"module_configs\" add constraint \"module_configs_module_name_unique\" unique (\"module_id\", \"name\");`);\n this.addSql(`alter table \"module_configs\" drop column \"organization_id\", drop column \"tenant_id\";`);\n }\n\n}\n"],
5
+ "mappings": "AAAA,SAAS,iBAAiB;AAEnB,MAAM,gCAAgC,UAAU;AAAA,EAErD,MAAe,KAAoB;AACjC,SAAK,OAAO,wGAAwG;AACpH,SAAK,OAAO,mFAAmF;AAC/F,SAAK,OAAO,yHAAyH;AACrI,SAAK,OAAO,0IAA0I;AACtJ,SAAK,OAAO,8GAA8G;AAAA,EAC5H;AAAA,EAEA,MAAe,OAAsB;AACnC,SAAK,OAAO,+DAA+D;AAC3E,SAAK,OAAO,sDAAsD;AAClE,SAAK,OAAO,sDAAsD;AAClE,SAAK,OAAO,+GAA+G;AAC3H,SAAK,OAAO,sFAAsF;AAAA,EACpG;AAEF;",
6
+ "names": []
7
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/customers/api/activities/route.ts"],
4
- "sourcesContent": ["/**\n * @deprecated Use /api/customers/interactions instead. This route is maintained\n * as a compatibility bridge per SPEC-046b and delegates writes to canonical\n * interaction commands.\n */\nimport { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { CustomerActivity, CustomerDeal, CustomerInteraction } from '../../data/entities'\nimport { User } from '@open-mercato/core/modules/auth/data/entities'\nimport { activityCreateSchema, activityUpdateSchema } from '../../data/validators'\nimport { createCustomersCrudOpenApi, createPagedListResponseSchema, defaultOkResponseSchema } from '../openapi'\nimport {\n mapInteractionRecordToActivitySummary,\n CUSTOMER_INTERACTION_ACTIVITY_ADAPTER_SOURCE,\n} from '../../lib/interactionCompatibility'\nimport { withOperationMetadata } from '../../lib/operationMetadata'\nimport { resolveCustomerInteractionFeatureFlags } from '../../lib/interactionFeatureFlags'\nimport { resolveCustomersRequestContext } from '../../lib/interactionRequestContext'\nimport { hydrateCanonicalInteractions } from '../../lib/interactionReadModel'\nimport { resolveCanonicalActivityTargetId } from '../../lib/legacyActivityBridge'\nimport { buildEmailVisibilityMikroFilter } from '../../lib/visibilityFilter'\n\nconst listSchema = z.object({\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(100).default(50),\n entityId: z.string().uuid().optional(),\n dealId: z.string().uuid().optional(),\n activityType: z.string().optional(),\n sortField: z.string().optional(),\n sortDir: z.enum(['asc', 'desc']).optional(),\n}).passthrough()\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.activities.view'] },\n POST: { requireAuth: true, requireFeatures: ['customers.activities.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['customers.activities.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['customers.activities.manage'] },\n}\n\nexport const metadata = routeMetadata\n\nconst activityCreateBodySchema = activityCreateSchema.omit({\n organizationId: true,\n tenantId: true,\n}).passthrough()\n\nconst activityUpdateBodySchema = activityUpdateSchema.omit({\n organizationId: true,\n tenantId: true,\n}).passthrough()\n\nconst activityDeleteBodySchema = z.object({\n id: z.string().uuid(),\n})\n\nconst ADAPTER_HEADERS = {\n Deprecation: 'true',\n Sunset: 'Tue, 30 Jun 2026 00:00:00 GMT',\n Link: '</api/customers/interactions>; rel=\"successor-version\"',\n}\n\n// Caps the per-source fetch window used by the deprecated merged (legacy +\n// canonical bridge) read path. Keeps memory bounded on tenants with large\n// activity history; deep-pagination beyond this window is not supported here \u2014\n// use /api/customers/interactions instead.\nconst MERGED_ACTIVITY_FETCH_CAP = 2000\n\ntype ActivityItem = {\n id: string\n activityType: string\n subject?: string | null\n body?: string | null\n occurredAt?: string | null\n createdAt: string\n appearanceIcon?: string | null\n appearanceColor?: string | null\n entityId?: string | null\n authorUserId?: string | null\n authorName?: string | null\n authorEmail?: string | null\n dealId?: string | null\n dealTitle?: string | null\n customValues?: Record<string, unknown> | null\n activityTypeLabel?: string | null\n}\n\ntype CanonicalActivityListResult = {\n items: ActivityItem[]\n total: number\n bridgeIds: Set<string>\n}\n\nfunction resolveGuardUserId(auth: {\n sub?: string | null\n userId?: string | null\n keyId?: string | null\n}): string {\n if (typeof auth.sub === 'string' && auth.sub.trim().length > 0) return auth.sub\n if (typeof auth.userId === 'string' && auth.userId.trim().length > 0) return auth.userId\n if (typeof auth.keyId === 'string' && auth.keyId.trim().length > 0) return auth.keyId\n return 'system'\n}\n\nfunction withAdapterHeaders(response: Response): Response {\n const headers = new Headers(response.headers)\n Object.entries(ADAPTER_HEADERS).forEach(([key, value]) => headers.set(key, value))\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n })\n}\n\nasync function legacyAdaptersDisabledResponse(): Promise<Response> {\n const { translate } = await resolveTranslations()\n return withAdapterHeaders(NextResponse.json(\n {\n error: translate(\n 'customers.interactions.legacyAdapters.disabled',\n 'This legacy adapter has been disabled. Use /api/customers/interactions instead.',\n ),\n },\n { status: 410 },\n ))\n}\n\nfunction buildLegacyOrderBy(sortField: string | undefined, sortDir: 'asc' | 'desc') {\n if (sortField === 'createdAt') {\n return { createdAt: sortDir }\n }\n return { occurredAt: sortDir, createdAt: sortDir } as const\n}\n\nfunction buildCanonicalOrderBy(sortField: string | undefined, sortDir: 'asc' | 'desc') {\n if (sortField === 'createdAt') {\n return { createdAt: sortDir }\n }\n return { occurredAt: sortDir, createdAt: sortDir } as const\n}\n\nfunction resolveActivitySortValue(item: ActivityItem, sortField: string | undefined): number {\n const raw =\n sortField === 'createdAt'\n ? item.createdAt\n : item.occurredAt ?? item.createdAt\n const timestamp = raw ? new Date(raw).getTime() : Number.NaN\n return Number.isNaN(timestamp) ? 0 : timestamp\n}\n\nfunction sortActivityItems(\n items: ActivityItem[],\n sortField: string | undefined,\n sortDir: 'asc' | 'desc',\n): ActivityItem[] {\n return [...items].sort((left, right) => {\n const leftValue = resolveActivitySortValue(left, sortField)\n const rightValue = resolveActivitySortValue(right, sortField)\n if (leftValue === rightValue) {\n return sortDir === 'asc'\n ? left.id.localeCompare(right.id)\n : right.id.localeCompare(left.id)\n }\n return sortDir === 'asc' ? leftValue - rightValue : rightValue - leftValue\n })\n}\n\nfunction paginateActivityItems(\n items: ActivityItem[],\n page: number,\n pageSize: number,\n): { items: ActivityItem[]; total: number } {\n const start = (page - 1) * pageSize\n return {\n items: items.slice(start, start + pageSize),\n total: items.length,\n }\n}\n\nexport async function decorateActivityItems(\n em: EntityManager,\n items: ActivityItem[],\n decryptionScope?: { tenantId: string; organizationId: string },\n): Promise<ActivityItem[]> {\n if (items.length === 0) return items\n\n const authorIds = Array.from(\n new Set(\n items\n .map((item) => (typeof item.authorUserId === 'string' ? item.authorUserId : null))\n .filter((value): value is string => !!value),\n ),\n )\n const dealIds = Array.from(\n new Set(\n items\n .map((item) => (typeof item.dealId === 'string' ? item.dealId : null))\n .filter((value): value is string => !!value),\n ),\n )\n\n if (dealIds.length > 0 && (!decryptionScope?.tenantId || !decryptionScope?.organizationId)) {\n const { translate } = await resolveTranslations()\n throw new CrudHttpError(400, {\n error: translate('customers.errors.tenant_required', 'Tenant context is required'),\n })\n }\n\n const [users, deals] = await Promise.all([\n authorIds.length > 0 ? em.find(User, { id: { $in: authorIds } }) : Promise.resolve([]),\n dealIds.length > 0 && decryptionScope\n ? findWithDecryption(\n em,\n CustomerDeal,\n { id: { $in: dealIds }, tenantId: decryptionScope.tenantId, organizationId: decryptionScope.organizationId },\n undefined,\n decryptionScope,\n )\n : Promise.resolve([]),\n ])\n\n const userMap = new Map(\n users.map((user) => [\n user.id,\n {\n name: user.name ?? null,\n email: user.email ?? null,\n },\n ]),\n )\n const dealMap = new Map(deals.map((deal) => [deal.id, deal.title]))\n\n return items.map((item) => ({\n ...item,\n activityTypeLabel: item.activityType,\n authorName: item.authorUserId ? userMap.get(item.authorUserId)?.name ?? null : null,\n authorEmail: item.authorUserId ? userMap.get(item.authorUserId)?.email ?? null : null,\n dealTitle: item.dealId ? dealMap.get(item.dealId) ?? null : null,\n }))\n}\n\nfunction mapLegacyActivity(activity: CustomerActivity): ActivityItem {\n return {\n id: activity.id,\n activityType: activity.activityType,\n subject: activity.subject ?? null,\n body: activity.body ?? null,\n occurredAt: activity.occurredAt ? activity.occurredAt.toISOString() : null,\n createdAt: activity.createdAt.toISOString(),\n appearanceIcon: activity.appearanceIcon ?? null,\n appearanceColor: activity.appearanceColor ?? null,\n entityId: typeof activity.entity === 'string' ? activity.entity : activity.entity.id,\n authorUserId: activity.authorUserId ?? null,\n dealId: activity.deal ? (typeof activity.deal === 'string' ? activity.deal : activity.deal.id) : null,\n customValues: null,\n }\n}\n\nasync function listCanonicalActivities(\n em: EntityManager,\n container: { resolve: (name: string) => unknown },\n auth: { tenantId: string | null; orgId: string | null; sub?: string | null; userId?: string | null; keyId?: string | null },\n selectedOrganizationId: string | null,\n tenantId: string,\n organizationIds: string[] | null,\n query: z.infer<typeof listSchema>,\n options?: { includeDeleted?: boolean; source?: string | string[] | null; paginate?: boolean },\n): Promise<CanonicalActivityListResult> {\n const where: Record<string, unknown> = {\n tenantId,\n interactionType: { $ne: 'task' },\n }\n if (!options?.includeDeleted) {\n where.deletedAt = null\n }\n if (organizationIds && organizationIds.length > 0) {\n where.organizationId = { $in: organizationIds }\n }\n if (query.entityId) where.entity = query.entityId\n if (query.dealId) where.dealId = query.dealId\n if (query.activityType) where.interactionType = query.activityType\n if (options?.source) {\n where.source = Array.isArray(options.source) ? { $in: options.source } : options.source\n }\n\n // Per-user email privacy: exclude other users' private email interactions from\n // the deprecated /activities surface (mirrors the /interactions Layer-1 filter).\n // v1 strict owner-only \u2014 no admin bypass (the filter ignores caller features).\n const activitiesViewerUserId = auth.keyId ? null : (auth.sub ?? auth.userId ?? null)\n Object.assign(\n where,\n buildEmailVisibilityMikroFilter({\n currentUserId: activitiesViewerUserId,\n userFeatures: undefined,\n }),\n )\n\n const findOptions = {\n orderBy: buildCanonicalOrderBy(query.sortField, query.sortDir ?? 'desc'),\n ...(options?.paginate === false\n ? {}\n : {\n offset: (query.page - 1) * query.pageSize,\n limit: query.pageSize,\n }),\n }\n\n const rows =\n options?.paginate === false\n ? await em.find(CustomerInteraction, where, findOptions)\n : (await em.findAndCount(CustomerInteraction, where, findOptions))[0]\n const total =\n options?.paginate === false\n ? rows.filter((row) => !row.deletedAt).length\n : await em.count(CustomerInteraction, where)\n\n const activeRows = rows.filter((row) => !row.deletedAt)\n const hydrated = await hydrateCanonicalInteractions({\n em,\n container,\n auth,\n selectedOrganizationId,\n interactions: activeRows,\n })\n const items = hydrated.map((row) => ({\n ...mapInteractionRecordToActivitySummary(row),\n customValues: row.customValues ?? null,\n activityTypeLabel: row.interactionType,\n }))\n\n return {\n items,\n total,\n bridgeIds: new Set(rows.map((row) => row.id)),\n }\n}\n\nasync function listLegacyActivities(\n em: EntityManager,\n tenantId: string,\n organizationIds: string[] | null,\n query: z.infer<typeof listSchema>,\n options?: { paginate?: boolean },\n selectedOrganizationId?: string | null,\n): Promise<{ items: ActivityItem[]; total: number }> {\n const where: Record<string, unknown> = { tenantId }\n if (organizationIds && organizationIds.length > 0) {\n where.organizationId = { $in: organizationIds }\n }\n if (query.entityId) where.entity = query.entityId\n if (query.dealId) where.deal = query.dealId\n if (query.activityType) where.activityType = query.activityType\n\n const findOptions = {\n populate: ['entity', 'deal'] as const,\n orderBy: buildLegacyOrderBy(query.sortField, query.sortDir ?? 'desc'),\n ...(options?.paginate === false\n ? {}\n : {\n offset: (query.page - 1) * query.pageSize,\n limit: query.pageSize,\n }),\n }\n\n const rows =\n options?.paginate === false\n ? await em.find(CustomerActivity, where, findOptions)\n : (await em.findAndCount(CustomerActivity, where, findOptions))[0]\n const total =\n options?.paginate === false\n ? rows.length\n : await em.count(CustomerActivity, where)\n\n return {\n items: await decorateActivityItems(\n em,\n rows.map(mapLegacyActivity),\n selectedOrganizationId ? { tenantId, organizationId: selectedOrganizationId } : undefined,\n ),\n total,\n }\n}\n\nexport async function GET(request: Request): Promise<Response> {\n try {\n const url = new URL(request.url)\n const query = listSchema.parse(Object.fromEntries(url.searchParams))\n const {\n auth,\n em,\n organizationIds,\n container,\n selectedOrganizationId,\n } = await resolveCustomersRequestContext(request)\n const flags = await resolveCustomerInteractionFeatureFlags(container, auth.tenantId)\n if (!flags.legacyAdapters) {\n return await legacyAdaptersDisabledResponse()\n }\n\n const sortDir = query.sortDir ?? 'desc'\n\n const result = flags.unified\n ? await listCanonicalActivities(\n em,\n container,\n auth,\n selectedOrganizationId,\n auth.tenantId,\n organizationIds,\n query,\n )\n : await (async () => {\n const windowSize = Math.min(\n MERGED_ACTIVITY_FETCH_CAP,\n Math.max(query.pageSize, query.page * query.pageSize + query.pageSize),\n )\n const windowedQuery = { ...query, page: 1, pageSize: windowSize }\n const [legacy, canonical] = await Promise.all([\n listLegacyActivities(\n em,\n auth.tenantId,\n organizationIds,\n windowedQuery,\n { paginate: true },\n selectedOrganizationId,\n ),\n listCanonicalActivities(\n em,\n container,\n auth,\n selectedOrganizationId,\n auth.tenantId,\n organizationIds,\n windowedQuery,\n {\n includeDeleted: true,\n paginate: true,\n source: CUSTOMER_INTERACTION_ACTIVITY_ADAPTER_SOURCE,\n },\n ),\n ])\n const merged = sortActivityItems(\n [\n ...legacy.items.filter((item) => !canonical.bridgeIds.has(item.id)),\n ...canonical.items,\n ],\n query.sortField,\n sortDir,\n )\n const paged = paginateActivityItems(merged, query.page, query.pageSize)\n return {\n items: paged.items,\n total: paged.total,\n }\n })()\n\n return withAdapterHeaders(\n NextResponse.json({\n items: result.items,\n total: result.total,\n page: query.page,\n pageSize: query.pageSize,\n totalPages: Math.max(1, Math.ceil(result.total / query.pageSize)),\n }),\n )\n } catch (err) {\n if (isCrudHttpError(err)) {\n return withAdapterHeaders(NextResponse.json(err.body, { status: err.status }))\n }\n if (err instanceof z.ZodError) {\n return withAdapterHeaders(\n NextResponse.json({ error: 'Validation failed', details: err.issues }, { status: 400 }),\n )\n }\n console.error('customers.activities.get failed', err)\n return withAdapterHeaders(\n NextResponse.json({ error: 'Internal server error' }, { status: 500 }),\n )\n }\n}\n\nexport async function POST(request: Request): Promise<Response> {\n try {\n const { commandContext, container, auth, selectedOrganizationId } = await resolveCustomersRequestContext(request)\n const flags = await resolveCustomerInteractionFeatureFlags(container, auth.tenantId)\n if (!flags.legacyAdapters) {\n return await legacyAdaptersDisabledResponse()\n }\n const body = await readJsonSafe<Record<string, unknown>>(request, {})\n const parsed = activityCreateBodySchema.parse(body)\n const guardUserId = resolveGuardUserId(auth)\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId,\n organizationId: selectedOrganizationId,\n userId: guardUserId,\n resourceKind: 'customers.activity',\n resourceId: parsed.entityId,\n operation: 'create',\n requestMethod: request.method,\n requestHeaders: request.headers,\n mutationPayload: parsed,\n })\n if (guardResult && !guardResult.ok) {\n return withAdapterHeaders(NextResponse.json(guardResult.body, { status: guardResult.status }))\n }\n const commandBus = container.resolve('commandBus') as CommandBus\n const { result, logEntry } = await commandBus.execute('customers.interactions.create', {\n input: {\n tenantId: auth.tenantId,\n organizationId: selectedOrganizationId ?? auth.orgId,\n entityId: parsed.entityId,\n interactionType: parsed.activityType,\n title: parsed.subject ?? null,\n body: parsed.body ?? null,\n occurredAt: parsed.occurredAt ?? null,\n status: parsed.occurredAt ? 'done' : 'planned',\n dealId: parsed.dealId ?? null,\n authorUserId: parsed.authorUserId ?? null,\n appearanceIcon: parsed.appearanceIcon ?? null,\n appearanceColor: parsed.appearanceColor ?? null,\n source: CUSTOMER_INTERACTION_ACTIVITY_ADAPTER_SOURCE,\n customFields: (parsed as Record<string, unknown>).customFields,\n customValues: (parsed as Record<string, unknown>).customValues,\n },\n ctx: commandContext,\n })\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId,\n organizationId: selectedOrganizationId,\n userId: guardUserId,\n resourceKind: 'customers.activity',\n resourceId: parsed.entityId,\n operation: 'create',\n requestMethod: request.method,\n requestHeaders: request.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n const createdId =\n result && typeof result === 'object' && 'interactionId' in result && typeof result.interactionId === 'string'\n ? result.interactionId\n : result && typeof result === 'object' && 'id' in result && typeof result.id === 'string'\n ? result.id\n : null\n\n return withAdapterHeaders(\n withOperationMetadata(\n NextResponse.json({ id: createdId }, { status: 201 }),\n logEntry,\n { resourceKind: 'customers.activity', resourceId: createdId },\n ),\n )\n } catch (err) {\n if (isCrudHttpError(err)) {\n return withAdapterHeaders(NextResponse.json(err.body, { status: err.status }))\n }\n if (err instanceof z.ZodError) {\n return withAdapterHeaders(\n NextResponse.json({ error: 'Validation failed', details: err.issues }, { status: 400 }),\n )\n }\n console.error('customers.activities.post failed', err)\n return withAdapterHeaders(\n NextResponse.json({ error: 'Internal server error' }, { status: 500 }),\n )\n }\n}\n\nexport async function PUT(request: Request): Promise<Response> {\n try {\n const { commandContext, container, em, auth, selectedOrganizationId } = await resolveCustomersRequestContext(request)\n const flags = await resolveCustomerInteractionFeatureFlags(container, auth.tenantId)\n if (!flags.legacyAdapters) {\n return await legacyAdaptersDisabledResponse()\n }\n const body = await readJsonSafe<Record<string, unknown>>(request, {})\n const parsed = activityUpdateBodySchema.parse(body)\n const guardUserId = resolveGuardUserId(auth)\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId,\n organizationId: selectedOrganizationId,\n userId: guardUserId,\n resourceKind: 'customers.activity',\n resourceId: parsed.id,\n operation: 'update',\n requestMethod: request.method,\n requestHeaders: request.headers,\n mutationPayload: parsed,\n })\n if (guardResult && !guardResult.ok) {\n return withAdapterHeaders(NextResponse.json(guardResult.body, { status: guardResult.status }))\n }\n const commandBus = container.resolve('commandBus') as CommandBus\n const interactionId = flags.unified\n ? parsed.id\n : await resolveCanonicalActivityTargetId(em, commandBus, commandContext, parsed.id, auth.tenantId)\n\n const { logEntry } = await commandBus.execute('customers.interactions.update', {\n input: {\n id: interactionId,\n interactionType: parsed.activityType,\n title: parsed.subject ?? undefined,\n body: parsed.body ?? undefined,\n occurredAt: parsed.occurredAt ?? undefined,\n status: parsed.occurredAt ? 'done' : undefined,\n dealId: parsed.dealId ?? undefined,\n authorUserId: parsed.authorUserId ?? undefined,\n appearanceIcon: parsed.appearanceIcon ?? undefined,\n appearanceColor: parsed.appearanceColor ?? undefined,\n customFields: (parsed as Record<string, unknown>).customFields,\n customValues: (parsed as Record<string, unknown>).customValues,\n },\n ctx: commandContext,\n })\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId,\n organizationId: selectedOrganizationId,\n userId: guardUserId,\n resourceKind: 'customers.activity',\n resourceId: parsed.id,\n operation: 'update',\n requestMethod: request.method,\n requestHeaders: request.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return withAdapterHeaders(\n withOperationMetadata(\n NextResponse.json({ ok: true }),\n logEntry,\n { resourceKind: 'customers.activity', resourceId: parsed.id },\n ),\n )\n } catch (err) {\n if (isCrudHttpError(err)) {\n return withAdapterHeaders(NextResponse.json(err.body, { status: err.status }))\n }\n if (err instanceof z.ZodError) {\n return withAdapterHeaders(\n NextResponse.json({ error: 'Validation failed', details: err.issues }, { status: 400 }),\n )\n }\n console.error('customers.activities.put failed', err)\n return withAdapterHeaders(\n NextResponse.json({ error: 'Internal server error' }, { status: 500 }),\n )\n }\n}\n\nexport async function DELETE(request: Request): Promise<Response> {\n try {\n const { commandContext, container, em, auth, selectedOrganizationId } = await resolveCustomersRequestContext(request)\n const flags = await resolveCustomerInteractionFeatureFlags(container, auth.tenantId)\n if (!flags.legacyAdapters) {\n return await legacyAdaptersDisabledResponse()\n }\n const body = await readJsonSafe<Record<string, unknown>>(request, {})\n const parsed = activityDeleteBodySchema.parse(body)\n const guardUserId = resolveGuardUserId(auth)\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId,\n organizationId: selectedOrganizationId,\n userId: guardUserId,\n resourceKind: 'customers.activity',\n resourceId: parsed.id,\n operation: 'delete',\n requestMethod: request.method,\n requestHeaders: request.headers,\n mutationPayload: parsed,\n })\n if (guardResult && !guardResult.ok) {\n return withAdapterHeaders(NextResponse.json(guardResult.body, { status: guardResult.status }))\n }\n const commandBus = container.resolve('commandBus') as CommandBus\n const interactionId = flags.unified\n ? parsed.id\n : await resolveCanonicalActivityTargetId(em, commandBus, commandContext, parsed.id, auth.tenantId)\n const { logEntry } = await commandBus.execute('customers.interactions.delete', {\n input: { id: interactionId },\n ctx: commandContext,\n })\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId,\n organizationId: selectedOrganizationId,\n userId: guardUserId,\n resourceKind: 'customers.activity',\n resourceId: parsed.id,\n operation: 'delete',\n requestMethod: request.method,\n requestHeaders: request.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n return withAdapterHeaders(\n withOperationMetadata(\n NextResponse.json({ ok: true }),\n logEntry,\n { resourceKind: 'customers.activity', resourceId: parsed.id },\n ),\n )\n } catch (err) {\n if (isCrudHttpError(err)) {\n return withAdapterHeaders(NextResponse.json(err.body, { status: err.status }))\n }\n if (err instanceof z.ZodError) {\n return withAdapterHeaders(\n NextResponse.json({ error: 'Validation failed', details: err.issues }, { status: 400 }),\n )\n }\n console.error('customers.activities.delete failed', err)\n return withAdapterHeaders(\n NextResponse.json({ error: 'Internal server error' }, { status: 500 }),\n )\n }\n}\n\nconst activityListItemSchema = z\n .object({\n id: z.string().uuid(),\n activityType: z.string(),\n subject: z.string().nullable().optional(),\n body: z.string().nullable().optional(),\n occurredAt: z.string().nullable().optional(),\n createdAt: z.string(),\n appearanceIcon: z.string().nullable().optional(),\n appearanceColor: z.string().nullable().optional(),\n entityId: z.string().uuid().nullable().optional(),\n authorUserId: z.string().uuid().nullable().optional(),\n authorName: z.string().nullable().optional(),\n authorEmail: z.string().nullable().optional(),\n dealId: z.string().uuid().nullable().optional(),\n dealTitle: z.string().nullable().optional(),\n customValues: z.record(z.string(), z.unknown()).nullable().optional(),\n activityTypeLabel: z.string().nullable().optional(),\n })\n .passthrough()\n\nconst activityCreateResponseSchema = z.object({\n id: z.string().uuid().nullable(),\n})\n\nexport const openApi: OpenApiRouteDoc = createCustomersCrudOpenApi({\n resourceName: 'Activity',\n querySchema: listSchema,\n listResponseSchema: createPagedListResponseSchema(activityListItemSchema),\n create: {\n schema: activityCreateBodySchema,\n responseSchema: activityCreateResponseSchema,\n description: 'DEPRECATED (sunset 2026-06-30): Creates a timeline activity. Use POST /api/customers/interactions instead.',\n },\n update: {\n schema: activityUpdateBodySchema,\n responseSchema: defaultOkResponseSchema,\n description: 'DEPRECATED (sunset 2026-06-30): Updates an activity. Use PUT /api/customers/interactions instead.',\n },\n del: {\n schema: activityDeleteBodySchema,\n responseSchema: defaultOkResponseSchema,\n description: 'DEPRECATED (sunset 2026-06-30): Deletes an activity. Use DELETE /api/customers/interactions instead.',\n },\n})\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * @deprecated Use /api/customers/interactions instead. This route is maintained\n * as a compatibility bridge per SPEC-046b and delegates writes to canonical\n * interaction commands.\n */\nimport { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { CustomerActivity, CustomerDeal, CustomerInteraction } from '../../data/entities'\nimport { User } from '@open-mercato/core/modules/auth/data/entities'\nimport { activityCreateSchema, activityUpdateSchema } from '../../data/validators'\nimport { createCustomersCrudOpenApi, createPagedListResponseSchema, defaultOkResponseSchema } from '../openapi'\nimport {\n mapInteractionRecordToActivitySummary,\n CUSTOMER_INTERACTION_ACTIVITY_ADAPTER_SOURCE,\n} from '../../lib/interactionCompatibility'\nimport { withOperationMetadata } from '../../lib/operationMetadata'\nimport { resolveCustomerInteractionFeatureFlags } from '../../lib/interactionFeatureFlags'\nimport { resolveCustomersRequestContext } from '../../lib/interactionRequestContext'\nimport { hydrateCanonicalInteractions } from '../../lib/interactionReadModel'\nimport { resolveCanonicalActivityTargetId } from '../../lib/legacyActivityBridge'\nimport { buildEmailVisibilityMikroFilter } from '../../lib/visibilityFilter'\n\nconst listSchema = z.object({\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(100).default(50),\n entityId: z.string().uuid().optional(),\n dealId: z.string().uuid().optional(),\n activityType: z.string().optional(),\n sortField: z.string().optional(),\n sortDir: z.enum(['asc', 'desc']).optional(),\n}).passthrough()\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['customers.activities.view'] },\n POST: { requireAuth: true, requireFeatures: ['customers.activities.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['customers.activities.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['customers.activities.manage'] },\n}\n\nexport const metadata = routeMetadata\n\nconst activityCreateBodySchema = activityCreateSchema.omit({\n organizationId: true,\n tenantId: true,\n}).passthrough()\n\nconst activityUpdateBodySchema = activityUpdateSchema.omit({\n organizationId: true,\n tenantId: true,\n}).passthrough()\n\nconst activityDeleteBodySchema = z.object({\n id: z.string().uuid(),\n})\n\nconst ADAPTER_HEADERS = {\n Deprecation: 'true',\n Sunset: 'Tue, 30 Jun 2026 00:00:00 GMT',\n Link: '</api/customers/interactions>; rel=\"successor-version\"',\n}\n\n// Caps the per-source fetch window used by the deprecated merged (legacy +\n// canonical bridge) read path. Keeps memory bounded on tenants with large\n// activity history; deep-pagination beyond this window is not supported here \u2014\n// use /api/customers/interactions instead.\n//\n// Audited for the #3386 rollout (P2): this merged path sorts only on\n// non-encrypted system timestamps (createdAt/occurredAt, see\n// resolveActivitySortValue), so the #3278 two-phase *encrypted*-sort migration\n// is intentionally not applied here. Merge-order correctness across the page\n// boundary is covered by __tests__/merged-pagination.test.ts.\nconst MERGED_ACTIVITY_FETCH_CAP = 2000\n\ntype ActivityItem = {\n id: string\n activityType: string\n subject?: string | null\n body?: string | null\n occurredAt?: string | null\n createdAt: string\n appearanceIcon?: string | null\n appearanceColor?: string | null\n entityId?: string | null\n authorUserId?: string | null\n authorName?: string | null\n authorEmail?: string | null\n dealId?: string | null\n dealTitle?: string | null\n customValues?: Record<string, unknown> | null\n activityTypeLabel?: string | null\n}\n\ntype CanonicalActivityListResult = {\n items: ActivityItem[]\n total: number\n bridgeIds: Set<string>\n}\n\nfunction resolveGuardUserId(auth: {\n sub?: string | null\n userId?: string | null\n keyId?: string | null\n}): string {\n if (typeof auth.sub === 'string' && auth.sub.trim().length > 0) return auth.sub\n if (typeof auth.userId === 'string' && auth.userId.trim().length > 0) return auth.userId\n if (typeof auth.keyId === 'string' && auth.keyId.trim().length > 0) return auth.keyId\n return 'system'\n}\n\nfunction withAdapterHeaders(response: Response): Response {\n const headers = new Headers(response.headers)\n Object.entries(ADAPTER_HEADERS).forEach(([key, value]) => headers.set(key, value))\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n })\n}\n\nasync function legacyAdaptersDisabledResponse(): Promise<Response> {\n const { translate } = await resolveTranslations()\n return withAdapterHeaders(NextResponse.json(\n {\n error: translate(\n 'customers.interactions.legacyAdapters.disabled',\n 'This legacy adapter has been disabled. Use /api/customers/interactions instead.',\n ),\n },\n { status: 410 },\n ))\n}\n\nfunction buildLegacyOrderBy(sortField: string | undefined, sortDir: 'asc' | 'desc') {\n if (sortField === 'createdAt') {\n return { createdAt: sortDir }\n }\n return { occurredAt: sortDir, createdAt: sortDir } as const\n}\n\nfunction buildCanonicalOrderBy(sortField: string | undefined, sortDir: 'asc' | 'desc') {\n if (sortField === 'createdAt') {\n return { createdAt: sortDir }\n }\n return { occurredAt: sortDir, createdAt: sortDir } as const\n}\n\nfunction resolveActivitySortValue(item: ActivityItem, sortField: string | undefined): number {\n const raw =\n sortField === 'createdAt'\n ? item.createdAt\n : item.occurredAt ?? item.createdAt\n const timestamp = raw ? new Date(raw).getTime() : Number.NaN\n return Number.isNaN(timestamp) ? 0 : timestamp\n}\n\nfunction sortActivityItems(\n items: ActivityItem[],\n sortField: string | undefined,\n sortDir: 'asc' | 'desc',\n): ActivityItem[] {\n return [...items].sort((left, right) => {\n const leftValue = resolveActivitySortValue(left, sortField)\n const rightValue = resolveActivitySortValue(right, sortField)\n if (leftValue === rightValue) {\n return sortDir === 'asc'\n ? left.id.localeCompare(right.id)\n : right.id.localeCompare(left.id)\n }\n return sortDir === 'asc' ? leftValue - rightValue : rightValue - leftValue\n })\n}\n\nfunction paginateActivityItems(\n items: ActivityItem[],\n page: number,\n pageSize: number,\n): { items: ActivityItem[]; total: number } {\n const start = (page - 1) * pageSize\n return {\n items: items.slice(start, start + pageSize),\n total: items.length,\n }\n}\n\nexport async function decorateActivityItems(\n em: EntityManager,\n items: ActivityItem[],\n decryptionScope?: { tenantId: string; organizationId: string },\n): Promise<ActivityItem[]> {\n if (items.length === 0) return items\n\n const authorIds = Array.from(\n new Set(\n items\n .map((item) => (typeof item.authorUserId === 'string' ? item.authorUserId : null))\n .filter((value): value is string => !!value),\n ),\n )\n const dealIds = Array.from(\n new Set(\n items\n .map((item) => (typeof item.dealId === 'string' ? item.dealId : null))\n .filter((value): value is string => !!value),\n ),\n )\n\n if (dealIds.length > 0 && (!decryptionScope?.tenantId || !decryptionScope?.organizationId)) {\n const { translate } = await resolveTranslations()\n throw new CrudHttpError(400, {\n error: translate('customers.errors.tenant_required', 'Tenant context is required'),\n })\n }\n\n const [users, deals] = await Promise.all([\n authorIds.length > 0 ? em.find(User, { id: { $in: authorIds } }) : Promise.resolve([]),\n dealIds.length > 0 && decryptionScope\n ? findWithDecryption(\n em,\n CustomerDeal,\n { id: { $in: dealIds }, tenantId: decryptionScope.tenantId, organizationId: decryptionScope.organizationId },\n undefined,\n decryptionScope,\n )\n : Promise.resolve([]),\n ])\n\n const userMap = new Map(\n users.map((user) => [\n user.id,\n {\n name: user.name ?? null,\n email: user.email ?? null,\n },\n ]),\n )\n const dealMap = new Map(deals.map((deal) => [deal.id, deal.title]))\n\n return items.map((item) => ({\n ...item,\n activityTypeLabel: item.activityType,\n authorName: item.authorUserId ? userMap.get(item.authorUserId)?.name ?? null : null,\n authorEmail: item.authorUserId ? userMap.get(item.authorUserId)?.email ?? null : null,\n dealTitle: item.dealId ? dealMap.get(item.dealId) ?? null : null,\n }))\n}\n\nfunction mapLegacyActivity(activity: CustomerActivity): ActivityItem {\n return {\n id: activity.id,\n activityType: activity.activityType,\n subject: activity.subject ?? null,\n body: activity.body ?? null,\n occurredAt: activity.occurredAt ? activity.occurredAt.toISOString() : null,\n createdAt: activity.createdAt.toISOString(),\n appearanceIcon: activity.appearanceIcon ?? null,\n appearanceColor: activity.appearanceColor ?? null,\n entityId: typeof activity.entity === 'string' ? activity.entity : activity.entity.id,\n authorUserId: activity.authorUserId ?? null,\n dealId: activity.deal ? (typeof activity.deal === 'string' ? activity.deal : activity.deal.id) : null,\n customValues: null,\n }\n}\n\nasync function listCanonicalActivities(\n em: EntityManager,\n container: { resolve: (name: string) => unknown },\n auth: { tenantId: string | null; orgId: string | null; sub?: string | null; userId?: string | null; keyId?: string | null },\n selectedOrganizationId: string | null,\n tenantId: string,\n organizationIds: string[] | null,\n query: z.infer<typeof listSchema>,\n options?: { includeDeleted?: boolean; source?: string | string[] | null; paginate?: boolean },\n): Promise<CanonicalActivityListResult> {\n const where: Record<string, unknown> = {\n tenantId,\n interactionType: { $ne: 'task' },\n }\n if (!options?.includeDeleted) {\n where.deletedAt = null\n }\n if (organizationIds && organizationIds.length > 0) {\n where.organizationId = { $in: organizationIds }\n }\n if (query.entityId) where.entity = query.entityId\n if (query.dealId) where.dealId = query.dealId\n if (query.activityType) where.interactionType = query.activityType\n if (options?.source) {\n where.source = Array.isArray(options.source) ? { $in: options.source } : options.source\n }\n\n // Per-user email privacy: exclude other users' private email interactions from\n // the deprecated /activities surface (mirrors the /interactions Layer-1 filter).\n // v1 strict owner-only \u2014 no admin bypass (the filter ignores caller features).\n const activitiesViewerUserId = auth.keyId ? null : (auth.sub ?? auth.userId ?? null)\n Object.assign(\n where,\n buildEmailVisibilityMikroFilter({\n currentUserId: activitiesViewerUserId,\n userFeatures: undefined,\n }),\n )\n\n const findOptions = {\n orderBy: buildCanonicalOrderBy(query.sortField, query.sortDir ?? 'desc'),\n ...(options?.paginate === false\n ? {}\n : {\n offset: (query.page - 1) * query.pageSize,\n limit: query.pageSize,\n }),\n }\n\n const rows =\n options?.paginate === false\n ? await em.find(CustomerInteraction, where, findOptions)\n : (await em.findAndCount(CustomerInteraction, where, findOptions))[0]\n const total =\n options?.paginate === false\n ? rows.filter((row) => !row.deletedAt).length\n : await em.count(CustomerInteraction, where)\n\n const activeRows = rows.filter((row) => !row.deletedAt)\n const hydrated = await hydrateCanonicalInteractions({\n em,\n container,\n auth,\n selectedOrganizationId,\n interactions: activeRows,\n })\n const items = hydrated.map((row) => ({\n ...mapInteractionRecordToActivitySummary(row),\n customValues: row.customValues ?? null,\n activityTypeLabel: row.interactionType,\n }))\n\n return {\n items,\n total,\n bridgeIds: new Set(rows.map((row) => row.id)),\n }\n}\n\nasync function listLegacyActivities(\n em: EntityManager,\n tenantId: string,\n organizationIds: string[] | null,\n query: z.infer<typeof listSchema>,\n options?: { paginate?: boolean },\n selectedOrganizationId?: string | null,\n): Promise<{ items: ActivityItem[]; total: number }> {\n const where: Record<string, unknown> = { tenantId }\n if (organizationIds && organizationIds.length > 0) {\n where.organizationId = { $in: organizationIds }\n }\n if (query.entityId) where.entity = query.entityId\n if (query.dealId) where.deal = query.dealId\n if (query.activityType) where.activityType = query.activityType\n\n const findOptions = {\n populate: ['entity', 'deal'] as const,\n orderBy: buildLegacyOrderBy(query.sortField, query.sortDir ?? 'desc'),\n ...(options?.paginate === false\n ? {}\n : {\n offset: (query.page - 1) * query.pageSize,\n limit: query.pageSize,\n }),\n }\n\n const rows =\n options?.paginate === false\n ? await em.find(CustomerActivity, where, findOptions)\n : (await em.findAndCount(CustomerActivity, where, findOptions))[0]\n const total =\n options?.paginate === false\n ? rows.length\n : await em.count(CustomerActivity, where)\n\n return {\n items: await decorateActivityItems(\n em,\n rows.map(mapLegacyActivity),\n selectedOrganizationId ? { tenantId, organizationId: selectedOrganizationId } : undefined,\n ),\n total,\n }\n}\n\nexport async function GET(request: Request): Promise<Response> {\n try {\n const url = new URL(request.url)\n const query = listSchema.parse(Object.fromEntries(url.searchParams))\n const {\n auth,\n em,\n organizationIds,\n container,\n selectedOrganizationId,\n } = await resolveCustomersRequestContext(request)\n const flags = await resolveCustomerInteractionFeatureFlags(container, auth.tenantId)\n if (!flags.legacyAdapters) {\n return await legacyAdaptersDisabledResponse()\n }\n\n const sortDir = query.sortDir ?? 'desc'\n\n const result = flags.unified\n ? await listCanonicalActivities(\n em,\n container,\n auth,\n selectedOrganizationId,\n auth.tenantId,\n organizationIds,\n query,\n )\n : await (async () => {\n const windowSize = Math.min(\n MERGED_ACTIVITY_FETCH_CAP,\n Math.max(query.pageSize, query.page * query.pageSize + query.pageSize),\n )\n const windowedQuery = { ...query, page: 1, pageSize: windowSize }\n const [legacy, canonical] = await Promise.all([\n listLegacyActivities(\n em,\n auth.tenantId,\n organizationIds,\n windowedQuery,\n { paginate: true },\n selectedOrganizationId,\n ),\n listCanonicalActivities(\n em,\n container,\n auth,\n selectedOrganizationId,\n auth.tenantId,\n organizationIds,\n windowedQuery,\n {\n includeDeleted: true,\n paginate: true,\n source: CUSTOMER_INTERACTION_ACTIVITY_ADAPTER_SOURCE,\n },\n ),\n ])\n const merged = sortActivityItems(\n [\n ...legacy.items.filter((item) => !canonical.bridgeIds.has(item.id)),\n ...canonical.items,\n ],\n query.sortField,\n sortDir,\n )\n const paged = paginateActivityItems(merged, query.page, query.pageSize)\n return {\n items: paged.items,\n total: paged.total,\n }\n })()\n\n return withAdapterHeaders(\n NextResponse.json({\n items: result.items,\n total: result.total,\n page: query.page,\n pageSize: query.pageSize,\n totalPages: Math.max(1, Math.ceil(result.total / query.pageSize)),\n }),\n )\n } catch (err) {\n if (isCrudHttpError(err)) {\n return withAdapterHeaders(NextResponse.json(err.body, { status: err.status }))\n }\n if (err instanceof z.ZodError) {\n return withAdapterHeaders(\n NextResponse.json({ error: 'Validation failed', details: err.issues }, { status: 400 }),\n )\n }\n console.error('customers.activities.get failed', err)\n return withAdapterHeaders(\n NextResponse.json({ error: 'Internal server error' }, { status: 500 }),\n )\n }\n}\n\nexport async function POST(request: Request): Promise<Response> {\n try {\n const { commandContext, container, auth, selectedOrganizationId } = await resolveCustomersRequestContext(request)\n const flags = await resolveCustomerInteractionFeatureFlags(container, auth.tenantId)\n if (!flags.legacyAdapters) {\n return await legacyAdaptersDisabledResponse()\n }\n const body = await readJsonSafe<Record<string, unknown>>(request, {})\n const parsed = activityCreateBodySchema.parse(body)\n const guardUserId = resolveGuardUserId(auth)\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId,\n organizationId: selectedOrganizationId,\n userId: guardUserId,\n resourceKind: 'customers.activity',\n resourceId: parsed.entityId,\n operation: 'create',\n requestMethod: request.method,\n requestHeaders: request.headers,\n mutationPayload: parsed,\n })\n if (guardResult && !guardResult.ok) {\n return withAdapterHeaders(NextResponse.json(guardResult.body, { status: guardResult.status }))\n }\n const commandBus = container.resolve('commandBus') as CommandBus\n const { result, logEntry } = await commandBus.execute('customers.interactions.create', {\n input: {\n tenantId: auth.tenantId,\n organizationId: selectedOrganizationId ?? auth.orgId,\n entityId: parsed.entityId,\n interactionType: parsed.activityType,\n title: parsed.subject ?? null,\n body: parsed.body ?? null,\n occurredAt: parsed.occurredAt ?? null,\n status: parsed.occurredAt ? 'done' : 'planned',\n dealId: parsed.dealId ?? null,\n authorUserId: parsed.authorUserId ?? null,\n appearanceIcon: parsed.appearanceIcon ?? null,\n appearanceColor: parsed.appearanceColor ?? null,\n source: CUSTOMER_INTERACTION_ACTIVITY_ADAPTER_SOURCE,\n customFields: (parsed as Record<string, unknown>).customFields,\n customValues: (parsed as Record<string, unknown>).customValues,\n },\n ctx: commandContext,\n })\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId,\n organizationId: selectedOrganizationId,\n userId: guardUserId,\n resourceKind: 'customers.activity',\n resourceId: parsed.entityId,\n operation: 'create',\n requestMethod: request.method,\n requestHeaders: request.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n const createdId =\n result && typeof result === 'object' && 'interactionId' in result && typeof result.interactionId === 'string'\n ? result.interactionId\n : result && typeof result === 'object' && 'id' in result && typeof result.id === 'string'\n ? result.id\n : null\n\n return withAdapterHeaders(\n withOperationMetadata(\n NextResponse.json({ id: createdId }, { status: 201 }),\n logEntry,\n { resourceKind: 'customers.activity', resourceId: createdId },\n ),\n )\n } catch (err) {\n if (isCrudHttpError(err)) {\n return withAdapterHeaders(NextResponse.json(err.body, { status: err.status }))\n }\n if (err instanceof z.ZodError) {\n return withAdapterHeaders(\n NextResponse.json({ error: 'Validation failed', details: err.issues }, { status: 400 }),\n )\n }\n console.error('customers.activities.post failed', err)\n return withAdapterHeaders(\n NextResponse.json({ error: 'Internal server error' }, { status: 500 }),\n )\n }\n}\n\nexport async function PUT(request: Request): Promise<Response> {\n try {\n const { commandContext, container, em, auth, selectedOrganizationId } = await resolveCustomersRequestContext(request)\n const flags = await resolveCustomerInteractionFeatureFlags(container, auth.tenantId)\n if (!flags.legacyAdapters) {\n return await legacyAdaptersDisabledResponse()\n }\n const body = await readJsonSafe<Record<string, unknown>>(request, {})\n const parsed = activityUpdateBodySchema.parse(body)\n const guardUserId = resolveGuardUserId(auth)\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId,\n organizationId: selectedOrganizationId,\n userId: guardUserId,\n resourceKind: 'customers.activity',\n resourceId: parsed.id,\n operation: 'update',\n requestMethod: request.method,\n requestHeaders: request.headers,\n mutationPayload: parsed,\n })\n if (guardResult && !guardResult.ok) {\n return withAdapterHeaders(NextResponse.json(guardResult.body, { status: guardResult.status }))\n }\n const commandBus = container.resolve('commandBus') as CommandBus\n const interactionId = flags.unified\n ? parsed.id\n : await resolveCanonicalActivityTargetId(em, commandBus, commandContext, parsed.id, auth.tenantId)\n\n const { logEntry } = await commandBus.execute('customers.interactions.update', {\n input: {\n id: interactionId,\n interactionType: parsed.activityType,\n title: parsed.subject ?? undefined,\n body: parsed.body ?? undefined,\n occurredAt: parsed.occurredAt ?? undefined,\n status: parsed.occurredAt ? 'done' : undefined,\n dealId: parsed.dealId ?? undefined,\n authorUserId: parsed.authorUserId ?? undefined,\n appearanceIcon: parsed.appearanceIcon ?? undefined,\n appearanceColor: parsed.appearanceColor ?? undefined,\n customFields: (parsed as Record<string, unknown>).customFields,\n customValues: (parsed as Record<string, unknown>).customValues,\n },\n ctx: commandContext,\n })\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId,\n organizationId: selectedOrganizationId,\n userId: guardUserId,\n resourceKind: 'customers.activity',\n resourceId: parsed.id,\n operation: 'update',\n requestMethod: request.method,\n requestHeaders: request.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return withAdapterHeaders(\n withOperationMetadata(\n NextResponse.json({ ok: true }),\n logEntry,\n { resourceKind: 'customers.activity', resourceId: parsed.id },\n ),\n )\n } catch (err) {\n if (isCrudHttpError(err)) {\n return withAdapterHeaders(NextResponse.json(err.body, { status: err.status }))\n }\n if (err instanceof z.ZodError) {\n return withAdapterHeaders(\n NextResponse.json({ error: 'Validation failed', details: err.issues }, { status: 400 }),\n )\n }\n console.error('customers.activities.put failed', err)\n return withAdapterHeaders(\n NextResponse.json({ error: 'Internal server error' }, { status: 500 }),\n )\n }\n}\n\nexport async function DELETE(request: Request): Promise<Response> {\n try {\n const { commandContext, container, em, auth, selectedOrganizationId } = await resolveCustomersRequestContext(request)\n const flags = await resolveCustomerInteractionFeatureFlags(container, auth.tenantId)\n if (!flags.legacyAdapters) {\n return await legacyAdaptersDisabledResponse()\n }\n const body = await readJsonSafe<Record<string, unknown>>(request, {})\n const parsed = activityDeleteBodySchema.parse(body)\n const guardUserId = resolveGuardUserId(auth)\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: auth.tenantId,\n organizationId: selectedOrganizationId,\n userId: guardUserId,\n resourceKind: 'customers.activity',\n resourceId: parsed.id,\n operation: 'delete',\n requestMethod: request.method,\n requestHeaders: request.headers,\n mutationPayload: parsed,\n })\n if (guardResult && !guardResult.ok) {\n return withAdapterHeaders(NextResponse.json(guardResult.body, { status: guardResult.status }))\n }\n const commandBus = container.resolve('commandBus') as CommandBus\n const interactionId = flags.unified\n ? parsed.id\n : await resolveCanonicalActivityTargetId(em, commandBus, commandContext, parsed.id, auth.tenantId)\n const { logEntry } = await commandBus.execute('customers.interactions.delete', {\n input: { id: interactionId },\n ctx: commandContext,\n })\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: auth.tenantId,\n organizationId: selectedOrganizationId,\n userId: guardUserId,\n resourceKind: 'customers.activity',\n resourceId: parsed.id,\n operation: 'delete',\n requestMethod: request.method,\n requestHeaders: request.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n return withAdapterHeaders(\n withOperationMetadata(\n NextResponse.json({ ok: true }),\n logEntry,\n { resourceKind: 'customers.activity', resourceId: parsed.id },\n ),\n )\n } catch (err) {\n if (isCrudHttpError(err)) {\n return withAdapterHeaders(NextResponse.json(err.body, { status: err.status }))\n }\n if (err instanceof z.ZodError) {\n return withAdapterHeaders(\n NextResponse.json({ error: 'Validation failed', details: err.issues }, { status: 400 }),\n )\n }\n console.error('customers.activities.delete failed', err)\n return withAdapterHeaders(\n NextResponse.json({ error: 'Internal server error' }, { status: 500 }),\n )\n }\n}\n\nconst activityListItemSchema = z\n .object({\n id: z.string().uuid(),\n activityType: z.string(),\n subject: z.string().nullable().optional(),\n body: z.string().nullable().optional(),\n occurredAt: z.string().nullable().optional(),\n createdAt: z.string(),\n appearanceIcon: z.string().nullable().optional(),\n appearanceColor: z.string().nullable().optional(),\n entityId: z.string().uuid().nullable().optional(),\n authorUserId: z.string().uuid().nullable().optional(),\n authorName: z.string().nullable().optional(),\n authorEmail: z.string().nullable().optional(),\n dealId: z.string().uuid().nullable().optional(),\n dealTitle: z.string().nullable().optional(),\n customValues: z.record(z.string(), z.unknown()).nullable().optional(),\n activityTypeLabel: z.string().nullable().optional(),\n })\n .passthrough()\n\nconst activityCreateResponseSchema = z.object({\n id: z.string().uuid().nullable(),\n})\n\nexport const openApi: OpenApiRouteDoc = createCustomersCrudOpenApi({\n resourceName: 'Activity',\n querySchema: listSchema,\n listResponseSchema: createPagedListResponseSchema(activityListItemSchema),\n create: {\n schema: activityCreateBodySchema,\n responseSchema: activityCreateResponseSchema,\n description: 'DEPRECATED (sunset 2026-06-30): Creates a timeline activity. Use POST /api/customers/interactions instead.',\n },\n update: {\n schema: activityUpdateBodySchema,\n responseSchema: defaultOkResponseSchema,\n description: 'DEPRECATED (sunset 2026-06-30): Updates an activity. Use PUT /api/customers/interactions instead.',\n },\n del: {\n schema: activityDeleteBodySchema,\n responseSchema: defaultOkResponseSchema,\n description: 'DEPRECATED (sunset 2026-06-30): Deletes an activity. Use DELETE /api/customers/interactions instead.',\n },\n})\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,4 +1,5 @@
1
1
  import { NextResponse } from "next/server";
2
+ import { raw } from "@mikro-orm/core";
2
3
  import { Resend } from "resend";
3
4
  import { parseBooleanWithDefault } from "@open-mercato/shared/lib/boolean";
4
5
  import { resolveDefaultEmailFromAddress } from "@open-mercato/shared/lib/email/config";
@@ -93,6 +94,50 @@ async function POST(req) {
93
94
  if (references && references.length > 0) {
94
95
  headers["References"] = references.join(" ");
95
96
  }
97
+ const sendClaimedAt = (/* @__PURE__ */ new Date()).toISOString();
98
+ const claimed = await ctx.em.nativeUpdate(
99
+ InboxProposalAction,
100
+ {
101
+ id: action.id,
102
+ proposalId: proposal.id,
103
+ actionType: "draft_reply",
104
+ organizationId: ctx.organizationId,
105
+ tenantId: ctx.tenantId,
106
+ deletedAt: null,
107
+ status: "executed",
108
+ [raw(`coalesce("metadata"->>'replySentAt', '')`)]: "",
109
+ [raw(`coalesce("metadata"->>'replySendClaimedAt', '')`)]: ""
110
+ },
111
+ {
112
+ metadata: raw(
113
+ `coalesce("metadata", '{}'::jsonb) || jsonb_build_object('replySendClaimedAt', ?::text)`,
114
+ [sendClaimedAt]
115
+ )
116
+ }
117
+ );
118
+ if (claimed === 0) {
119
+ return NextResponse.json(
120
+ { error: "Reply send is already in progress or has been sent for this action" },
121
+ { status: 409 }
122
+ );
123
+ }
124
+ const releaseSendClaim = async () => {
125
+ try {
126
+ await ctx.em.nativeUpdate(
127
+ InboxProposalAction,
128
+ {
129
+ id: action.id,
130
+ organizationId: ctx.organizationId,
131
+ tenantId: ctx.tenantId,
132
+ [raw(`coalesce("metadata"->>'replySentAt', '')`)]: "",
133
+ [raw(`("metadata"->>'replySendClaimedAt')`)]: sendClaimedAt
134
+ },
135
+ { metadata: raw(`"metadata" - 'replySendClaimedAt'`) }
136
+ );
137
+ } catch (releaseError) {
138
+ console.error("[inbox_ops:reply:send] Failed to release send claim:", releaseError);
139
+ }
140
+ };
96
141
  const resend = new Resend(apiKey);
97
142
  const { data: sendData, error: sendError } = await resend.emails.send({
98
143
  to: toAddress,
@@ -102,6 +147,7 @@ async function POST(req) {
102
147
  ...Object.keys(headers).length > 0 ? { headers } : {}
103
148
  });
104
149
  if (sendError) {
150
+ await releaseSendClaim();
105
151
  const errorMessage = sendError.message || "Unknown error";
106
152
  return NextResponse.json({ error: `Failed to send email: ${errorMessage}` }, { status: 502 });
107
153
  }
@@ -158,7 +204,7 @@ const openApi = {
158
204
  { status: 200, description: "Reply sent successfully" },
159
205
  { status: 400, description: "Missing required payload fields" },
160
206
  { status: 404, description: "Reply action not found" },
161
- { status: 409, description: "Action in invalid state for sending" },
207
+ { status: 409, description: "Action in invalid state for sending, or a send is already in progress / completed for this reply" },
162
208
  { status: 502, description: "Email delivery failed" },
163
209
  { status: 503, description: "Email service not configured or disabled" }
164
210
  ]