@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.
- package/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +8 -0
- package/dist/generated/entities/module_config/index.js +4 -0
- package/dist/generated/entities/module_config/index.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +2 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/auth/lib/setup-app.js +1 -1
- package/dist/modules/auth/lib/setup-app.js.map +2 -2
- package/dist/modules/catalog/components/products/ProductComplianceSection.js +235 -62
- package/dist/modules/catalog/components/products/ProductComplianceSection.js.map +2 -2
- package/dist/modules/configs/components/CachePanel.js +2 -1
- package/dist/modules/configs/components/CachePanel.js.map +2 -2
- package/dist/modules/configs/components/SystemStatusPanel.js +10 -12
- package/dist/modules/configs/components/SystemStatusPanel.js.map +2 -2
- package/dist/modules/configs/data/entities.js +15 -1
- package/dist/modules/configs/data/entities.js.map +2 -2
- package/dist/modules/configs/lib/module-config-service.js +52 -19
- package/dist/modules/configs/lib/module-config-service.js.map +3 -3
- package/dist/modules/configs/migrations/Migration20260617150000.js +21 -0
- package/dist/modules/configs/migrations/Migration20260617150000.js.map +7 -0
- package/dist/modules/customers/api/activities/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/proposals/[id]/replies/[replyId]/send/route.js +47 -1
- package/dist/modules/inbox_ops/api/proposals/[id]/replies/[replyId]/send/route.js.map +2 -2
- package/generated/entities/module_config/index.ts +2 -0
- package/generated/entity-fields-registry.ts +2 -0
- package/package.json +7 -7
- package/src/modules/auth/lib/setup-app.ts +1 -1
- package/src/modules/catalog/components/products/ProductComplianceSection.tsx +138 -27
- package/src/modules/configs/components/CachePanel.tsx +2 -3
- package/src/modules/configs/components/SystemStatusPanel.tsx +12 -19
- package/src/modules/configs/data/entities.ts +17 -1
- package/src/modules/configs/lib/module-config-service.ts +83 -24
- package/src/modules/configs/migrations/.snapshot-open-mercato.json +41 -4
- package/src/modules/configs/migrations/.snapshot-openmercato.json +41 -4
- package/src/modules/configs/migrations/Migration20260617150000.ts +21 -0
- package/src/modules/customers/api/activities/route.ts +6 -0
- 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 = "
|
|
3
|
+
const CACHE_VERSION = "v2";
|
|
4
4
|
const CACHE_TTL_MS = 6e4;
|
|
5
|
-
const
|
|
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
|
-
|
|
75
|
-
|
|
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(
|
|
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
|
-
|
|
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 = '
|
|
5
|
-
"mappings": "AAGA,SAAS,oBAAoB;AAC7B,SAAS,6BAA6B;AAEtC,MAAM,gBAAgB;AACtB,MAAM,eAAe;AAOrB,MAAM,WAAW,CAAC,UAAkB,
|
|
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
|
]
|