@open-mercato/core 0.6.7-develop.6631.1.9d64b49ddc → 0.6.7-develop.6646.1.be515f3d49
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/dist/modules/attachments/components/AttachmentLibrary.js +13 -2
- package/dist/modules/attachments/components/AttachmentLibrary.js.map +2 -2
- package/dist/modules/catalog/di.js +1 -1
- package/dist/modules/catalog/di.js.map +2 -2
- package/dist/modules/dashboards/lib/widgets.js +5 -3
- package/dist/modules/dashboards/lib/widgets.js.map +2 -2
- package/dist/modules/directory/api/get/organizations/lookup.js +51 -3
- package/dist/modules/directory/api/get/organizations/lookup.js.map +2 -2
- package/dist/modules/directory/api/get/tenants/lookup.js +31 -1
- package/dist/modules/directory/api/get/tenants/lookup.js.map +2 -2
- package/dist/modules/notifications/di.js +1 -1
- package/dist/modules/notifications/di.js.map +2 -2
- package/dist/modules/planner/commands/availability-rule-sets.js +37 -15
- package/dist/modules/planner/commands/availability-rule-sets.js.map +2 -2
- package/dist/modules/planner/commands/availability.js +37 -11
- package/dist/modules/planner/commands/availability.js.map +2 -2
- package/dist/modules/planner/commands/shared.js +34 -1
- package/dist/modules/planner/commands/shared.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/attachments/components/AttachmentLibrary.tsx +9 -3
- package/src/modules/catalog/di.ts +3 -1
- package/src/modules/dashboards/lib/widgets.ts +5 -3
- package/src/modules/directory/api/get/organizations/lookup.ts +87 -2
- package/src/modules/directory/api/get/tenants/lookup.ts +46 -0
- package/src/modules/notifications/di.ts +1 -1
- package/src/modules/planner/commands/availability-rule-sets.ts +38 -14
- package/src/modules/planner/commands/availability.ts +44 -11
- package/src/modules/planner/commands/shared.ts +51 -0
|
@@ -2,6 +2,10 @@ import { NextResponse } from "next/server";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
4
4
|
import { Tenant } from "@open-mercato/core/modules/directory/data/entities";
|
|
5
|
+
import { getCachedRateLimiterService } from "@open-mercato/core/bootstrap";
|
|
6
|
+
import { readEndpointRateLimitConfig } from "@open-mercato/shared/lib/ratelimit/config";
|
|
7
|
+
import { checkRateLimit, getClientIp, rateLimitErrorSchema } from "@open-mercato/shared/lib/ratelimit/helpers";
|
|
8
|
+
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
5
9
|
const metadata = {
|
|
6
10
|
path: "/directory/tenants/lookup",
|
|
7
11
|
GET: {
|
|
@@ -11,7 +15,32 @@ const metadata = {
|
|
|
11
15
|
const tenantLookupQuerySchema = z.object({
|
|
12
16
|
tenantId: z.string().uuid()
|
|
13
17
|
});
|
|
18
|
+
const tenantLookupIpRateLimitConfig = readEndpointRateLimitConfig("DIRECTORY_TENANT_LOOKUP", {
|
|
19
|
+
points: 60,
|
|
20
|
+
duration: 60,
|
|
21
|
+
blockDuration: 60,
|
|
22
|
+
keyPrefix: "directory-tenant-lookup"
|
|
23
|
+
});
|
|
24
|
+
async function enforceTenantLookupRateLimit(req) {
|
|
25
|
+
try {
|
|
26
|
+
const rateLimiterService = getCachedRateLimiterService();
|
|
27
|
+
if (!rateLimiterService) return null;
|
|
28
|
+
const clientIp = getClientIp(req, rateLimiterService.trustProxyDepth);
|
|
29
|
+
if (!clientIp) return null;
|
|
30
|
+
const { translate } = await resolveTranslations();
|
|
31
|
+
return await checkRateLimit(
|
|
32
|
+
rateLimiterService,
|
|
33
|
+
tenantLookupIpRateLimitConfig,
|
|
34
|
+
clientIp,
|
|
35
|
+
translate("api.errors.rateLimit", "Too many requests. Please try again later.")
|
|
36
|
+
);
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
14
41
|
async function GET(req) {
|
|
42
|
+
const rateLimitResponse = await enforceTenantLookupRateLimit(req);
|
|
43
|
+
if (rateLimitResponse) return rateLimitResponse;
|
|
15
44
|
const url = new URL(req.url);
|
|
16
45
|
const tenantId = url.searchParams.get("tenantId") || url.searchParams.get("tenant") || "";
|
|
17
46
|
const parsed = tenantLookupQuerySchema.safeParse({ tenantId });
|
|
@@ -51,7 +80,8 @@ const tenantLookupDoc = {
|
|
|
51
80
|
],
|
|
52
81
|
errors: [
|
|
53
82
|
{ status: 400, description: "Invalid tenant id", schema: tenantLookupErrorSchema },
|
|
54
|
-
{ status: 404, description: "Tenant not found", schema: tenantLookupErrorSchema }
|
|
83
|
+
{ status: 404, description: "Tenant not found", schema: tenantLookupErrorSchema },
|
|
84
|
+
{ status: 429, description: "Too many tenant lookups", schema: rateLimitErrorSchema }
|
|
55
85
|
]
|
|
56
86
|
};
|
|
57
87
|
const openApi = {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/directory/api/get/tenants/lookup.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { Tenant } from '@open-mercato/core/modules/directory/data/entities'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\n\nexport const metadata = {\n path: '/directory/tenants/lookup',\n GET: {\n requireAuth: false,\n },\n}\n\nconst tenantLookupQuerySchema = z.object({\n tenantId: z.string().uuid(),\n})\n\nexport async function GET(req: Request) {\n const url = new URL(req.url)\n const tenantId = url.searchParams.get('tenantId') || url.searchParams.get('tenant') || ''\n const parsed = tenantLookupQuerySchema.safeParse({ tenantId })\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: 'Invalid tenant id.' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const em = (container.resolve('em') as EntityManager)\n const tenant = await em.findOne(Tenant, { id: parsed.data.tenantId, deletedAt: null })\n if (!tenant) {\n return NextResponse.json({ ok: false, error: 'Tenant not found.' }, { status: 404 })\n }\n return NextResponse.json({\n ok: true,\n tenant: { id: String(tenant.id), name: tenant.name },\n })\n}\n\nconst lookupTag = 'Directory'\n\nconst tenantLookupSuccessSchema = z.object({\n ok: z.literal(true),\n tenant: z.object({\n id: z.string().uuid(),\n name: z.string(),\n }),\n})\n\nconst tenantLookupErrorSchema = z.object({\n ok: z.literal(false),\n error: z.string(),\n})\n\nconst tenantLookupDoc: OpenApiMethodDoc = {\n summary: 'Public tenant lookup',\n description: 'Resolves tenant metadata for login/activation flows.',\n tags: [lookupTag],\n query: tenantLookupQuerySchema,\n responses: [\n { status: 200, description: 'Tenant resolved.', schema: tenantLookupSuccessSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid tenant id', schema: tenantLookupErrorSchema },\n { status: 404, description: 'Tenant not found', schema: tenantLookupErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: lookupTag,\n summary: 'Public tenant lookup',\n methods: {\n GET: tenantLookupDoc,\n },\n}\n\nexport default GET\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,8BAA8B;AACvC,SAAS,cAAc;
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { Tenant } from '@open-mercato/core/modules/directory/data/entities'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { getCachedRateLimiterService } from '@open-mercato/core/bootstrap'\nimport { readEndpointRateLimitConfig } from '@open-mercato/shared/lib/ratelimit/config'\nimport { checkRateLimit, getClientIp, rateLimitErrorSchema } from '@open-mercato/shared/lib/ratelimit/helpers'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\n\nexport const metadata = {\n path: '/directory/tenants/lookup',\n GET: {\n requireAuth: false,\n },\n}\n\nconst tenantLookupQuerySchema = z.object({\n tenantId: z.string().uuid(),\n})\n\n// Unauthenticated login/onboarding resolver: it returns the tenant name to anyone\n// holding the id, so cap probes per IP to keep name-harvesting from leaked ids \u2014\n// and the unauthenticated DB query behind it \u2014 bounded and noisy.\n//\n// The cap is deliberately generous: the login screen resolves the tenant on every\n// render and a workforce behind one NAT egress IP shares this budget, while a\n// tripped lookup renders as an invalid tenant. 60/min/IP stays clear of that.\n// Tune per deployment with RATE_LIMIT_DIRECTORY_TENANT_LOOKUP_POINTS / _DURATION /\n// _BLOCK_DURATION.\nconst tenantLookupIpRateLimitConfig = readEndpointRateLimitConfig('DIRECTORY_TENANT_LOOKUP', {\n points: 60,\n duration: 60,\n blockDuration: 60,\n keyPrefix: 'directory-tenant-lookup',\n})\n\n// Fail-open, like auth's checkAuthRateLimit: this resolver sits on the unauthenticated\n// login bootstrap path, so a limiter outage must degrade to unlimited lookups rather\n// than 500 the login screen.\nasync function enforceTenantLookupRateLimit(req: Request): Promise<NextResponse | null> {\n try {\n const rateLimiterService = getCachedRateLimiterService()\n if (!rateLimiterService) return null\n const clientIp = getClientIp(req, rateLimiterService.trustProxyDepth)\n if (!clientIp) return null\n const { translate } = await resolveTranslations()\n return await checkRateLimit(\n rateLimiterService,\n tenantLookupIpRateLimitConfig,\n clientIp,\n translate('api.errors.rateLimit', 'Too many requests. Please try again later.'),\n )\n } catch {\n return null\n }\n}\n\nexport async function GET(req: Request) {\n // Consume the limit before validation or DB work, so junk input cannot bypass it.\n const rateLimitResponse = await enforceTenantLookupRateLimit(req)\n if (rateLimitResponse) return rateLimitResponse\n\n const url = new URL(req.url)\n const tenantId = url.searchParams.get('tenantId') || url.searchParams.get('tenant') || ''\n const parsed = tenantLookupQuerySchema.safeParse({ tenantId })\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: 'Invalid tenant id.' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const em = (container.resolve('em') as EntityManager)\n const tenant = await em.findOne(Tenant, { id: parsed.data.tenantId, deletedAt: null })\n if (!tenant) {\n return NextResponse.json({ ok: false, error: 'Tenant not found.' }, { status: 404 })\n }\n return NextResponse.json({\n ok: true,\n tenant: { id: String(tenant.id), name: tenant.name },\n })\n}\n\nconst lookupTag = 'Directory'\n\nconst tenantLookupSuccessSchema = z.object({\n ok: z.literal(true),\n tenant: z.object({\n id: z.string().uuid(),\n name: z.string(),\n }),\n})\n\nconst tenantLookupErrorSchema = z.object({\n ok: z.literal(false),\n error: z.string(),\n})\n\nconst tenantLookupDoc: OpenApiMethodDoc = {\n summary: 'Public tenant lookup',\n description: 'Resolves tenant metadata for login/activation flows.',\n tags: [lookupTag],\n query: tenantLookupQuerySchema,\n responses: [\n { status: 200, description: 'Tenant resolved.', schema: tenantLookupSuccessSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid tenant id', schema: tenantLookupErrorSchema },\n { status: 404, description: 'Tenant not found', schema: tenantLookupErrorSchema },\n { status: 429, description: 'Too many tenant lookups', schema: rateLimitErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: lookupTag,\n summary: 'Public tenant lookup',\n methods: {\n GET: tenantLookupDoc,\n },\n}\n\nexport default GET\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/modules/notifications/di.ts"],
|
|
4
|
-
"sourcesContent": ["import type { AppContainer } from '@open-mercato/shared/lib/di/container'\nimport { asFunction } from 'awilix'\nimport { createNotificationService } from './lib/notificationService'\n\nexport function register(container: AppContainer): void {\n container.register({\n notificationService: asFunction(({ em, eventBus, commandBus }) =>\n createNotificationService({ em, eventBus, commandBus })\n ).scoped(),\n })\n}\n"],
|
|
5
|
-
"mappings": "AACA,SAAS,kBAAkB;AAC3B,SAAS,iCAAiC;AAEnC,SAAS,SAAS,WAA+B;AACtD,YAAU,SAAS;AAAA,IACjB,qBAAqB;AAAA,MAAW,CAAC,EAAE,IAAI,UAAU,WAAW,MAC1D,0BAA0B,EAAE,IAAI,UAAU,WAAW,CAAC;AAAA,IACxD,EAAE,OAAO;AAAA,
|
|
4
|
+
"sourcesContent": ["import type { AppContainer } from '@open-mercato/shared/lib/di/container'\nimport { asFunction } from 'awilix'\nimport { createNotificationService } from './lib/notificationService'\n\nexport function register(container: AppContainer): void {\n container.register({\n notificationService: asFunction(({ em, eventBus, commandBus }) =>\n createNotificationService({ em, eventBus, commandBus })\n ).scoped().proxy(),\n })\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,kBAAkB;AAC3B,SAAS,iCAAiC;AAEnC,SAAS,SAAS,WAA+B;AACtD,YAAU,SAAS;AAAA,IACjB,qBAAqB;AAAA,MAAW,CAAC,EAAE,IAAI,UAAU,WAAW,MAC1D,0BAA0B,EAAE,IAAI,UAAU,WAAW,CAAC;AAAA,IACxD,EAAE,OAAO,EAAE,MAAM;AAAA,EACnB,CAAC;AACH;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -11,18 +11,26 @@ import {
|
|
|
11
11
|
} from "../data/validators.js";
|
|
12
12
|
import { plannerAvailabilityRuleSetCrudEvents } from "../lib/crud.js";
|
|
13
13
|
import { makeCreateRedo } from "@open-mercato/shared/lib/commands/redo";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
applyScopeToWhere,
|
|
16
|
+
commandActorScope,
|
|
17
|
+
ensureOrganizationScope,
|
|
18
|
+
ensureTenantScope,
|
|
19
|
+
explicitPlannerCommandScope,
|
|
20
|
+
extractUndoPayload,
|
|
21
|
+
scopeForDecryption
|
|
22
|
+
} from "./shared.js";
|
|
15
23
|
import { E } from "../../../generated/entities.ids.generated.js";
|
|
16
24
|
const availabilityRuleSetCrudIndexer = {
|
|
17
25
|
entityType: E.planner.planner_availability_rule_set
|
|
18
26
|
};
|
|
19
|
-
async function loadAvailabilityRuleSetSnapshot(em, id) {
|
|
27
|
+
async function loadAvailabilityRuleSetSnapshot(em, id, scope) {
|
|
20
28
|
const ruleSet = await findOneWithDecryption(
|
|
21
29
|
em,
|
|
22
30
|
PlannerAvailabilityRuleSet,
|
|
23
|
-
{ id },
|
|
31
|
+
applyScopeToWhere({ id }, scope),
|
|
24
32
|
void 0,
|
|
25
|
-
|
|
33
|
+
scopeForDecryption(scope)
|
|
26
34
|
);
|
|
27
35
|
if (!ruleSet) return null;
|
|
28
36
|
const custom = await loadCustomFieldSnapshot(em, {
|
|
@@ -85,15 +93,23 @@ const createAvailabilityRuleSetCommand = {
|
|
|
85
93
|
});
|
|
86
94
|
return { ruleSetId: record.id };
|
|
87
95
|
},
|
|
88
|
-
captureAfter: async (
|
|
96
|
+
captureAfter: async (input, result, ctx) => {
|
|
89
97
|
const em = ctx.container.resolve("em").fork();
|
|
90
|
-
const snapshot = await loadAvailabilityRuleSetSnapshot(
|
|
98
|
+
const snapshot = await loadAvailabilityRuleSetSnapshot(
|
|
99
|
+
em,
|
|
100
|
+
result.ruleSetId,
|
|
101
|
+
explicitPlannerCommandScope(input.tenantId, input.organizationId)
|
|
102
|
+
);
|
|
91
103
|
if (!snapshot) return null;
|
|
92
104
|
return snapshot;
|
|
93
105
|
},
|
|
94
|
-
buildLog: async ({ result, ctx }) => {
|
|
106
|
+
buildLog: async ({ input, result, ctx }) => {
|
|
95
107
|
const em = ctx.container.resolve("em").fork();
|
|
96
|
-
const snapshot = await loadAvailabilityRuleSetSnapshot(
|
|
108
|
+
const snapshot = await loadAvailabilityRuleSetSnapshot(
|
|
109
|
+
em,
|
|
110
|
+
result.ruleSetId,
|
|
111
|
+
explicitPlannerCommandScope(input.tenantId, input.organizationId)
|
|
112
|
+
);
|
|
97
113
|
if (!snapshot) return null;
|
|
98
114
|
const { translate } = await resolveTranslations();
|
|
99
115
|
return {
|
|
@@ -159,19 +175,20 @@ const updateAvailabilityRuleSetCommand = {
|
|
|
159
175
|
async prepare(input, ctx) {
|
|
160
176
|
const { parsed } = parseWithCustomFields(plannerAvailabilityRuleSetUpdateSchema, input);
|
|
161
177
|
const em = ctx.container.resolve("em");
|
|
162
|
-
const snapshot = await loadAvailabilityRuleSetSnapshot(em, parsed.id);
|
|
178
|
+
const snapshot = await loadAvailabilityRuleSetSnapshot(em, parsed.id, commandActorScope(ctx));
|
|
163
179
|
if (!snapshot) return {};
|
|
164
180
|
return { before: snapshot };
|
|
165
181
|
},
|
|
166
182
|
async execute(input, ctx) {
|
|
167
183
|
const { parsed, custom } = parseWithCustomFields(plannerAvailabilityRuleSetUpdateSchema, input);
|
|
168
184
|
const em = ctx.container.resolve("em").fork();
|
|
185
|
+
const scope = commandActorScope(ctx);
|
|
169
186
|
const record = await findOneWithDecryption(
|
|
170
187
|
em,
|
|
171
188
|
PlannerAvailabilityRuleSet,
|
|
172
|
-
{ id: parsed.id, deletedAt: null },
|
|
189
|
+
applyScopeToWhere({ id: parsed.id, deletedAt: null }, scope),
|
|
173
190
|
void 0,
|
|
174
|
-
|
|
191
|
+
scopeForDecryption(scope)
|
|
175
192
|
);
|
|
176
193
|
if (!record) throw new CrudHttpError(404, { error: "Planner availability rule set not found." });
|
|
177
194
|
ensureTenantScope(ctx, record.tenantId);
|
|
@@ -208,7 +225,11 @@ const updateAvailabilityRuleSetCommand = {
|
|
|
208
225
|
const before = snapshots.before;
|
|
209
226
|
if (!before) return null;
|
|
210
227
|
const em = ctx.container.resolve("em").fork();
|
|
211
|
-
const after = await loadAvailabilityRuleSetSnapshot(
|
|
228
|
+
const after = await loadAvailabilityRuleSetSnapshot(
|
|
229
|
+
em,
|
|
230
|
+
before.id,
|
|
231
|
+
explicitPlannerCommandScope(before.tenantId, before.organizationId)
|
|
232
|
+
);
|
|
212
233
|
if (!after) return null;
|
|
213
234
|
const changes = buildChanges(before, after, [
|
|
214
235
|
"name",
|
|
@@ -284,7 +305,7 @@ const deleteAvailabilityRuleSetCommand = {
|
|
|
284
305
|
const id = input?.id;
|
|
285
306
|
if (!id) throw new CrudHttpError(400, { error: "Availability rule set id is required." });
|
|
286
307
|
const em = ctx.container.resolve("em");
|
|
287
|
-
const snapshot = await loadAvailabilityRuleSetSnapshot(em, id);
|
|
308
|
+
const snapshot = await loadAvailabilityRuleSetSnapshot(em, id, commandActorScope(ctx));
|
|
288
309
|
if (!snapshot) return {};
|
|
289
310
|
return { before: snapshot };
|
|
290
311
|
},
|
|
@@ -292,12 +313,13 @@ const deleteAvailabilityRuleSetCommand = {
|
|
|
292
313
|
const id = input?.id;
|
|
293
314
|
if (!id) throw new CrudHttpError(400, { error: "Availability rule set id is required." });
|
|
294
315
|
const em = ctx.container.resolve("em").fork();
|
|
316
|
+
const scope = commandActorScope(ctx);
|
|
295
317
|
const record = await findOneWithDecryption(
|
|
296
318
|
em,
|
|
297
319
|
PlannerAvailabilityRuleSet,
|
|
298
|
-
{ id, deletedAt: null },
|
|
320
|
+
applyScopeToWhere({ id, deletedAt: null }, scope),
|
|
299
321
|
void 0,
|
|
300
|
-
|
|
322
|
+
scopeForDecryption(scope)
|
|
301
323
|
);
|
|
302
324
|
if (!record) throw new CrudHttpError(404, { error: "Planner availability rule set not found." });
|
|
303
325
|
ensureTenantScope(ctx, record.tenantId);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/planner/commands/availability-rule-sets.ts"],
|
|
4
|
-
"sourcesContent": ["import type { CommandHandler } from '@open-mercato/shared/lib/commands'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { buildChanges, emitCrudSideEffects, emitCrudUndoSideEffects, parseWithCustomFields, setCustomFieldsIfAny } from '@open-mercato/shared/lib/commands/helpers'\nimport { buildCustomFieldResetMap, diffCustomFieldChanges, loadCustomFieldSnapshot, type CustomFieldSnapshot } from '@open-mercato/shared/lib/commands/customFieldSnapshots'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport type { CrudIndexerConfig } from '@open-mercato/shared/lib/crud/types'\nimport { PlannerAvailabilityRuleSet } from '../data/entities'\nimport {\n plannerAvailabilityRuleSetCreateSchema,\n plannerAvailabilityRuleSetUpdateSchema,\n type PlannerAvailabilityRuleSetCreateInput,\n type PlannerAvailabilityRuleSetUpdateInput,\n} from '../data/validators'\nimport { plannerAvailabilityRuleSetCrudEvents } from '../lib/crud'\nimport { makeCreateRedo } from '@open-mercato/shared/lib/commands/redo'\nimport { ensureOrganizationScope, ensureTenantScope, extractUndoPayload } from './shared'\nimport { E } from '#generated/entities.ids.generated'\n\nconst availabilityRuleSetCrudIndexer: CrudIndexerConfig<PlannerAvailabilityRuleSet> = {\n entityType: E.planner.planner_availability_rule_set,\n}\n\ntype AvailabilityRuleSetSnapshot = {\n id: string\n tenantId: string\n organizationId: string\n name: string\n description: string | null\n timezone: string\n deletedAt: string | null\n custom?: CustomFieldSnapshot\n}\n\ntype AvailabilityRuleSetUndoPayload = {\n before?: AvailabilityRuleSetSnapshot | null\n after?: AvailabilityRuleSetSnapshot | null\n}\n\nasync function loadAvailabilityRuleSetSnapshot(\n em: EntityManager,\n id: string,\n): Promise<AvailabilityRuleSetSnapshot | null> {\n const ruleSet = await findOneWithDecryption(\n em,\n PlannerAvailabilityRuleSet,\n { id },\n undefined,\n { tenantId: null, organizationId: null },\n )\n if (!ruleSet) return null\n const custom = await loadCustomFieldSnapshot(em, {\n entityId: E.planner.planner_availability_rule_set,\n recordId: ruleSet.id,\n tenantId: ruleSet.tenantId,\n organizationId: ruleSet.organizationId,\n })\n return {\n id: ruleSet.id,\n tenantId: ruleSet.tenantId,\n organizationId: ruleSet.organizationId,\n name: ruleSet.name,\n description: ruleSet.description ?? null,\n timezone: ruleSet.timezone,\n deletedAt: ruleSet.deletedAt ? ruleSet.deletedAt.toISOString() : null,\n custom,\n }\n}\n\nconst createAvailabilityRuleSetCommand: CommandHandler<PlannerAvailabilityRuleSetCreateInput, { ruleSetId: string }> = {\n id: 'planner.availability-rule-sets.create',\n async execute(input, ctx) {\n const { parsed, custom } = parseWithCustomFields(plannerAvailabilityRuleSetCreateSchema, input)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const now = new Date()\n const record = em.create(PlannerAvailabilityRuleSet, {\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n name: parsed.name,\n description: parsed.description ?? null,\n timezone: parsed.timezone,\n createdAt: now,\n updatedAt: now,\n deletedAt: null,\n })\n em.persist(record)\n await em.flush()\n const dataEngine = (ctx.container.resolve('dataEngine') as DataEngine)\n await setCustomFieldsIfAny({\n dataEngine,\n entityId: E.planner.planner_availability_rule_set,\n recordId: record.id,\n tenantId: record.tenantId,\n organizationId: record.organizationId,\n values: custom,\n })\n await emitCrudSideEffects({\n dataEngine,\n action: 'created',\n entity: record,\n identifiers: {\n id: record.id,\n organizationId: record.organizationId,\n tenantId: record.tenantId,\n },\n events: plannerAvailabilityRuleSetCrudEvents,\n indexer: availabilityRuleSetCrudIndexer,\n })\n return { ruleSetId: record.id }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const snapshot = await loadAvailabilityRuleSetSnapshot(em, result.ruleSetId)\n if (!snapshot) return null\n return snapshot\n },\n buildLog: async ({ result, ctx }) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const snapshot = await loadAvailabilityRuleSetSnapshot(em, result.ruleSetId)\n if (!snapshot) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('planner.audit.availabilityRuleSets.create', 'Create availability schedule'),\n resourceKind: 'planner.availabilityRuleSet',\n resourceId: snapshot.id,\n tenantId: snapshot.tenantId,\n organizationId: snapshot.organizationId,\n snapshotAfter: snapshot,\n payload: {\n undo: {\n after: snapshot,\n } satisfies AvailabilityRuleSetUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<AvailabilityRuleSetUndoPayload>(logEntry)\n const after = payload?.after\n if (!after) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const ruleSet = await em.findOne(PlannerAvailabilityRuleSet, { id: after.id })\n if (ruleSet) {\n ruleSet.deletedAt = new Date()\n ruleSet.updatedAt = new Date()\n await em.flush()\n\n const dataEngine = (ctx.container.resolve('dataEngine') as DataEngine)\n await emitCrudUndoSideEffects({\n dataEngine,\n action: 'deleted',\n entity: ruleSet,\n identifiers: {\n id: ruleSet.id,\n organizationId: ruleSet.organizationId,\n tenantId: ruleSet.tenantId,\n },\n events: plannerAvailabilityRuleSetCrudEvents,\n indexer: availabilityRuleSetCrudIndexer,\n })\n }\n },\n redo: makeCreateRedo<PlannerAvailabilityRuleSet, AvailabilityRuleSetSnapshot, PlannerAvailabilityRuleSetCreateInput, { ruleSetId: string }>({\n entityClass: PlannerAvailabilityRuleSet,\n getSnapshotId: (snapshot) => snapshot.id,\n seedFromSnapshot: (snapshot) => ({\n id: snapshot.id,\n tenantId: snapshot.tenantId,\n organizationId: snapshot.organizationId,\n name: snapshot.name,\n description: snapshot.description ?? null,\n timezone: snapshot.timezone,\n createdAt: new Date(),\n updatedAt: new Date(),\n deletedAt: null,\n }),\n buildResult: (entity) => ({ ruleSetId: entity.id }),\n events: plannerAvailabilityRuleSetCrudEvents,\n indexer: availabilityRuleSetCrudIndexer,\n }),\n}\n\nconst updateAvailabilityRuleSetCommand: CommandHandler<PlannerAvailabilityRuleSetUpdateInput, { ruleSetId: string }> = {\n id: 'planner.availability-rule-sets.update',\n async prepare(input, ctx) {\n const { parsed } = parseWithCustomFields(plannerAvailabilityRuleSetUpdateSchema, input)\n const em = (ctx.container.resolve('em') as EntityManager)\n const snapshot = await loadAvailabilityRuleSetSnapshot(em, parsed.id)\n if (!snapshot) return {}\n return { before: snapshot }\n },\n async execute(input, ctx) {\n const { parsed, custom } = parseWithCustomFields(plannerAvailabilityRuleSetUpdateSchema, input)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const record = await findOneWithDecryption(\n em,\n PlannerAvailabilityRuleSet,\n { id: parsed.id, deletedAt: null },\n undefined,\n { tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.auth?.orgId ?? null },\n )\n if (!record) throw new CrudHttpError(404, { error: 'Planner availability rule set not found.' })\n ensureTenantScope(ctx, record.tenantId)\n ensureOrganizationScope(ctx, record.organizationId)\n\n if (parsed.name !== undefined) record.name = parsed.name\n if (parsed.description !== undefined) record.description = parsed.description ?? null\n if (parsed.timezone !== undefined) record.timezone = parsed.timezone\n record.updatedAt = new Date()\n await em.flush()\n const dataEngine = (ctx.container.resolve('dataEngine') as DataEngine)\n await setCustomFieldsIfAny({\n dataEngine,\n entityId: E.planner.planner_availability_rule_set,\n recordId: record.id,\n tenantId: record.tenantId,\n organizationId: record.organizationId,\n values: custom,\n })\n await emitCrudSideEffects({\n dataEngine,\n action: 'updated',\n entity: record,\n identifiers: {\n id: record.id,\n organizationId: record.organizationId,\n tenantId: record.tenantId,\n },\n events: plannerAvailabilityRuleSetCrudEvents,\n indexer: availabilityRuleSetCrudIndexer,\n })\n return { ruleSetId: record.id }\n },\n buildLog: async ({ snapshots, ctx }) => {\n const before = snapshots.before as AvailabilityRuleSetSnapshot | undefined\n if (!before) return null\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const after = await loadAvailabilityRuleSetSnapshot(em, before.id)\n if (!after) return null\n const changes = buildChanges(before as unknown as Record<string, unknown>, after as unknown as Record<string, unknown>, [\n 'name',\n 'description',\n 'timezone',\n 'deletedAt',\n ])\n const customChanges = diffCustomFieldChanges(before.custom, after.custom)\n if (Object.keys(customChanges).length) {\n changes.customFields = { from: before.custom ?? null, to: after.custom ?? null }\n }\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('planner.audit.availabilityRuleSets.update', 'Update availability schedule'),\n resourceKind: 'planner.availabilityRuleSet',\n resourceId: before.id,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n snapshotBefore: before,\n snapshotAfter: after,\n changes,\n payload: {\n undo: {\n before,\n after,\n } satisfies AvailabilityRuleSetUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<AvailabilityRuleSetUndoPayload>(logEntry)\n const before = payload?.before ?? (logEntry?.snapshotBefore as AvailabilityRuleSetSnapshot | null | undefined)\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const ruleSet = await em.findOne(PlannerAvailabilityRuleSet, { id: before.id })\n if (!ruleSet) return\n ruleSet.name = before.name\n ruleSet.description = before.description ?? null\n ruleSet.timezone = before.timezone\n ruleSet.deletedAt = before.deletedAt ? new Date(before.deletedAt) : null\n ruleSet.updatedAt = new Date()\n await em.flush()\n\n const dataEngine = (ctx.container.resolve('dataEngine') as DataEngine)\n const afterSnapshot = payload?.after ?? (logEntry?.snapshotAfter as AvailabilityRuleSetSnapshot | null | undefined)\n if (before.custom || afterSnapshot?.custom) {\n const reset = buildCustomFieldResetMap(before.custom, afterSnapshot?.custom)\n await setCustomFieldsIfAny({\n dataEngine,\n entityId: E.planner.planner_availability_rule_set,\n recordId: ruleSet.id,\n tenantId: ruleSet.tenantId,\n organizationId: ruleSet.organizationId,\n values: reset,\n })\n }\n\n await emitCrudUndoSideEffects({\n dataEngine,\n action: 'updated',\n entity: ruleSet,\n identifiers: {\n id: ruleSet.id,\n organizationId: ruleSet.organizationId,\n tenantId: ruleSet.tenantId,\n },\n events: plannerAvailabilityRuleSetCrudEvents,\n indexer: availabilityRuleSetCrudIndexer,\n })\n },\n}\n\nconst deleteAvailabilityRuleSetCommand: CommandHandler<{ id?: string }, { ruleSetId: string }> = {\n id: 'planner.availability-rule-sets.delete',\n async prepare(input, ctx) {\n const id = input?.id\n if (!id) throw new CrudHttpError(400, { error: 'Availability rule set id is required.' })\n const em = (ctx.container.resolve('em') as EntityManager)\n const snapshot = await loadAvailabilityRuleSetSnapshot(em, id)\n if (!snapshot) return {}\n return { before: snapshot }\n },\n async execute(input, ctx) {\n const id = input?.id\n if (!id) throw new CrudHttpError(400, { error: 'Availability rule set id is required.' })\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const record = await findOneWithDecryption(\n em,\n PlannerAvailabilityRuleSet,\n { id, deletedAt: null },\n undefined,\n { tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.auth?.orgId ?? null },\n )\n if (!record) throw new CrudHttpError(404, { error: 'Planner availability rule set not found.' })\n ensureTenantScope(ctx, record.tenantId)\n ensureOrganizationScope(ctx, record.organizationId)\n\n record.deletedAt = new Date()\n record.updatedAt = new Date()\n await em.flush()\n\n const dataEngine = (ctx.container.resolve('dataEngine') as DataEngine)\n await emitCrudSideEffects({\n dataEngine,\n action: 'deleted',\n entity: record,\n identifiers: {\n id: record.id,\n organizationId: record.organizationId,\n tenantId: record.tenantId,\n },\n events: plannerAvailabilityRuleSetCrudEvents,\n indexer: availabilityRuleSetCrudIndexer,\n })\n return { ruleSetId: record.id }\n },\n buildLog: async ({ snapshots }) => {\n const before = snapshots.before as AvailabilityRuleSetSnapshot | undefined\n if (!before) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('planner.audit.availabilityRuleSets.delete', 'Delete availability schedule'),\n resourceKind: 'planner.availabilityRuleSet',\n resourceId: before.id,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n snapshotBefore: before,\n payload: {\n undo: {\n before,\n } satisfies AvailabilityRuleSetUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<AvailabilityRuleSetUndoPayload>(logEntry)\n const before = payload?.before ?? (logEntry?.snapshotBefore as AvailabilityRuleSetSnapshot | null | undefined)\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n let ruleSet = await em.findOne(PlannerAvailabilityRuleSet, { id: before.id })\n if (!ruleSet) {\n ruleSet = em.create(PlannerAvailabilityRuleSet, {\n id: before.id,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n name: before.name,\n description: before.description ?? null,\n timezone: before.timezone,\n deletedAt: null,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(ruleSet)\n } else {\n ruleSet.name = before.name\n ruleSet.description = before.description ?? null\n ruleSet.timezone = before.timezone\n ruleSet.deletedAt = null\n ruleSet.updatedAt = new Date()\n }\n await em.flush()\n\n const dataEngine = (ctx.container.resolve('dataEngine') as DataEngine)\n if (before.custom) {\n const reset = buildCustomFieldResetMap(before.custom, undefined)\n await setCustomFieldsIfAny({\n dataEngine,\n entityId: E.planner.planner_availability_rule_set,\n recordId: ruleSet.id,\n tenantId: ruleSet.tenantId,\n organizationId: ruleSet.organizationId,\n values: reset,\n })\n }\n\n await emitCrudUndoSideEffects({\n dataEngine,\n action: 'created',\n entity: ruleSet,\n identifiers: {\n id: ruleSet.id,\n organizationId: ruleSet.organizationId,\n tenantId: ruleSet.tenantId,\n },\n events: plannerAvailabilityRuleSetCrudEvents,\n indexer: availabilityRuleSetCrudIndexer,\n })\n },\n}\n\nregisterCommand(createAvailabilityRuleSetCommand)\nregisterCommand(updateAvailabilityRuleSetCommand)\nregisterCommand(deleteAvailabilityRuleSetCommand)\n"],
|
|
5
|
-
"mappings": "AACA,SAAS,uBAAuB;AAEhC,SAAS,qBAAqB;AAC9B,SAAS,2BAA2B;AACpC,SAAS,6BAA6B;AACtC,SAAS,cAAc,qBAAqB,yBAAyB,uBAAuB,4BAA4B;AACxH,SAAS,0BAA0B,wBAAwB,+BAAyD;AAGpH,SAAS,kCAAkC;AAC3C;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AACP,SAAS,4CAA4C;AACrD,SAAS,sBAAsB;AAC/B,
|
|
4
|
+
"sourcesContent": ["import type { CommandHandler } from '@open-mercato/shared/lib/commands'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { buildChanges, emitCrudSideEffects, emitCrudUndoSideEffects, parseWithCustomFields, setCustomFieldsIfAny } from '@open-mercato/shared/lib/commands/helpers'\nimport { buildCustomFieldResetMap, diffCustomFieldChanges, loadCustomFieldSnapshot, type CustomFieldSnapshot } from '@open-mercato/shared/lib/commands/customFieldSnapshots'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport type { CrudIndexerConfig } from '@open-mercato/shared/lib/crud/types'\nimport { PlannerAvailabilityRuleSet } from '../data/entities'\nimport {\n plannerAvailabilityRuleSetCreateSchema,\n plannerAvailabilityRuleSetUpdateSchema,\n type PlannerAvailabilityRuleSetCreateInput,\n type PlannerAvailabilityRuleSetUpdateInput,\n} from '../data/validators'\nimport { plannerAvailabilityRuleSetCrudEvents } from '../lib/crud'\nimport { makeCreateRedo } from '@open-mercato/shared/lib/commands/redo'\nimport {\n applyScopeToWhere,\n commandActorScope,\n ensureOrganizationScope,\n ensureTenantScope,\n explicitPlannerCommandScope,\n extractUndoPayload,\n scopeForDecryption,\n type PlannerCommandScope,\n} from './shared'\nimport { E } from '#generated/entities.ids.generated'\n\nconst availabilityRuleSetCrudIndexer: CrudIndexerConfig<PlannerAvailabilityRuleSet> = {\n entityType: E.planner.planner_availability_rule_set,\n}\n\ntype AvailabilityRuleSetSnapshot = {\n id: string\n tenantId: string\n organizationId: string\n name: string\n description: string | null\n timezone: string\n deletedAt: string | null\n custom?: CustomFieldSnapshot\n}\n\ntype AvailabilityRuleSetUndoPayload = {\n before?: AvailabilityRuleSetSnapshot | null\n after?: AvailabilityRuleSetSnapshot | null\n}\n\nasync function loadAvailabilityRuleSetSnapshot(\n em: EntityManager,\n id: string,\n scope: PlannerCommandScope,\n): Promise<AvailabilityRuleSetSnapshot | null> {\n const ruleSet = await findOneWithDecryption(\n em,\n PlannerAvailabilityRuleSet,\n applyScopeToWhere<PlannerAvailabilityRuleSet>({ id }, scope),\n undefined,\n scopeForDecryption(scope),\n )\n if (!ruleSet) return null\n const custom = await loadCustomFieldSnapshot(em, {\n entityId: E.planner.planner_availability_rule_set,\n recordId: ruleSet.id,\n tenantId: ruleSet.tenantId,\n organizationId: ruleSet.organizationId,\n })\n return {\n id: ruleSet.id,\n tenantId: ruleSet.tenantId,\n organizationId: ruleSet.organizationId,\n name: ruleSet.name,\n description: ruleSet.description ?? null,\n timezone: ruleSet.timezone,\n deletedAt: ruleSet.deletedAt ? ruleSet.deletedAt.toISOString() : null,\n custom,\n }\n}\n\nconst createAvailabilityRuleSetCommand: CommandHandler<PlannerAvailabilityRuleSetCreateInput, { ruleSetId: string }> = {\n id: 'planner.availability-rule-sets.create',\n async execute(input, ctx) {\n const { parsed, custom } = parseWithCustomFields(plannerAvailabilityRuleSetCreateSchema, input)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const now = new Date()\n const record = em.create(PlannerAvailabilityRuleSet, {\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n name: parsed.name,\n description: parsed.description ?? null,\n timezone: parsed.timezone,\n createdAt: now,\n updatedAt: now,\n deletedAt: null,\n })\n em.persist(record)\n await em.flush()\n const dataEngine = (ctx.container.resolve('dataEngine') as DataEngine)\n await setCustomFieldsIfAny({\n dataEngine,\n entityId: E.planner.planner_availability_rule_set,\n recordId: record.id,\n tenantId: record.tenantId,\n organizationId: record.organizationId,\n values: custom,\n })\n await emitCrudSideEffects({\n dataEngine,\n action: 'created',\n entity: record,\n identifiers: {\n id: record.id,\n organizationId: record.organizationId,\n tenantId: record.tenantId,\n },\n events: plannerAvailabilityRuleSetCrudEvents,\n indexer: availabilityRuleSetCrudIndexer,\n })\n return { ruleSetId: record.id }\n },\n captureAfter: async (input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const snapshot = await loadAvailabilityRuleSetSnapshot(\n em,\n result.ruleSetId,\n explicitPlannerCommandScope(input.tenantId, input.organizationId),\n )\n if (!snapshot) return null\n return snapshot\n },\n buildLog: async ({ input, result, ctx }) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const snapshot = await loadAvailabilityRuleSetSnapshot(\n em,\n result.ruleSetId,\n explicitPlannerCommandScope(input.tenantId, input.organizationId),\n )\n if (!snapshot) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('planner.audit.availabilityRuleSets.create', 'Create availability schedule'),\n resourceKind: 'planner.availabilityRuleSet',\n resourceId: snapshot.id,\n tenantId: snapshot.tenantId,\n organizationId: snapshot.organizationId,\n snapshotAfter: snapshot,\n payload: {\n undo: {\n after: snapshot,\n } satisfies AvailabilityRuleSetUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<AvailabilityRuleSetUndoPayload>(logEntry)\n const after = payload?.after\n if (!after) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const ruleSet = await em.findOne(PlannerAvailabilityRuleSet, { id: after.id })\n if (ruleSet) {\n ruleSet.deletedAt = new Date()\n ruleSet.updatedAt = new Date()\n await em.flush()\n\n const dataEngine = (ctx.container.resolve('dataEngine') as DataEngine)\n await emitCrudUndoSideEffects({\n dataEngine,\n action: 'deleted',\n entity: ruleSet,\n identifiers: {\n id: ruleSet.id,\n organizationId: ruleSet.organizationId,\n tenantId: ruleSet.tenantId,\n },\n events: plannerAvailabilityRuleSetCrudEvents,\n indexer: availabilityRuleSetCrudIndexer,\n })\n }\n },\n redo: makeCreateRedo<PlannerAvailabilityRuleSet, AvailabilityRuleSetSnapshot, PlannerAvailabilityRuleSetCreateInput, { ruleSetId: string }>({\n entityClass: PlannerAvailabilityRuleSet,\n getSnapshotId: (snapshot) => snapshot.id,\n seedFromSnapshot: (snapshot) => ({\n id: snapshot.id,\n tenantId: snapshot.tenantId,\n organizationId: snapshot.organizationId,\n name: snapshot.name,\n description: snapshot.description ?? null,\n timezone: snapshot.timezone,\n createdAt: new Date(),\n updatedAt: new Date(),\n deletedAt: null,\n }),\n buildResult: (entity) => ({ ruleSetId: entity.id }),\n events: plannerAvailabilityRuleSetCrudEvents,\n indexer: availabilityRuleSetCrudIndexer,\n }),\n}\n\nconst updateAvailabilityRuleSetCommand: CommandHandler<PlannerAvailabilityRuleSetUpdateInput, { ruleSetId: string }> = {\n id: 'planner.availability-rule-sets.update',\n async prepare(input, ctx) {\n const { parsed } = parseWithCustomFields(plannerAvailabilityRuleSetUpdateSchema, input)\n const em = (ctx.container.resolve('em') as EntityManager)\n const snapshot = await loadAvailabilityRuleSetSnapshot(em, parsed.id, commandActorScope(ctx))\n if (!snapshot) return {}\n return { before: snapshot }\n },\n async execute(input, ctx) {\n const { parsed, custom } = parseWithCustomFields(plannerAvailabilityRuleSetUpdateSchema, input)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const scope = commandActorScope(ctx)\n const record = await findOneWithDecryption(\n em,\n PlannerAvailabilityRuleSet,\n applyScopeToWhere<PlannerAvailabilityRuleSet>({ id: parsed.id, deletedAt: null }, scope),\n undefined,\n scopeForDecryption(scope),\n )\n if (!record) throw new CrudHttpError(404, { error: 'Planner availability rule set not found.' })\n ensureTenantScope(ctx, record.tenantId)\n ensureOrganizationScope(ctx, record.organizationId)\n\n if (parsed.name !== undefined) record.name = parsed.name\n if (parsed.description !== undefined) record.description = parsed.description ?? null\n if (parsed.timezone !== undefined) record.timezone = parsed.timezone\n record.updatedAt = new Date()\n await em.flush()\n const dataEngine = (ctx.container.resolve('dataEngine') as DataEngine)\n await setCustomFieldsIfAny({\n dataEngine,\n entityId: E.planner.planner_availability_rule_set,\n recordId: record.id,\n tenantId: record.tenantId,\n organizationId: record.organizationId,\n values: custom,\n })\n await emitCrudSideEffects({\n dataEngine,\n action: 'updated',\n entity: record,\n identifiers: {\n id: record.id,\n organizationId: record.organizationId,\n tenantId: record.tenantId,\n },\n events: plannerAvailabilityRuleSetCrudEvents,\n indexer: availabilityRuleSetCrudIndexer,\n })\n return { ruleSetId: record.id }\n },\n buildLog: async ({ snapshots, ctx }) => {\n const before = snapshots.before as AvailabilityRuleSetSnapshot | undefined\n if (!before) return null\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const after = await loadAvailabilityRuleSetSnapshot(\n em,\n before.id,\n explicitPlannerCommandScope(before.tenantId, before.organizationId),\n )\n if (!after) return null\n const changes = buildChanges(before as unknown as Record<string, unknown>, after as unknown as Record<string, unknown>, [\n 'name',\n 'description',\n 'timezone',\n 'deletedAt',\n ])\n const customChanges = diffCustomFieldChanges(before.custom, after.custom)\n if (Object.keys(customChanges).length) {\n changes.customFields = { from: before.custom ?? null, to: after.custom ?? null }\n }\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('planner.audit.availabilityRuleSets.update', 'Update availability schedule'),\n resourceKind: 'planner.availabilityRuleSet',\n resourceId: before.id,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n snapshotBefore: before,\n snapshotAfter: after,\n changes,\n payload: {\n undo: {\n before,\n after,\n } satisfies AvailabilityRuleSetUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<AvailabilityRuleSetUndoPayload>(logEntry)\n const before = payload?.before ?? (logEntry?.snapshotBefore as AvailabilityRuleSetSnapshot | null | undefined)\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const ruleSet = await em.findOne(PlannerAvailabilityRuleSet, { id: before.id })\n if (!ruleSet) return\n ruleSet.name = before.name\n ruleSet.description = before.description ?? null\n ruleSet.timezone = before.timezone\n ruleSet.deletedAt = before.deletedAt ? new Date(before.deletedAt) : null\n ruleSet.updatedAt = new Date()\n await em.flush()\n\n const dataEngine = (ctx.container.resolve('dataEngine') as DataEngine)\n const afterSnapshot = payload?.after ?? (logEntry?.snapshotAfter as AvailabilityRuleSetSnapshot | null | undefined)\n if (before.custom || afterSnapshot?.custom) {\n const reset = buildCustomFieldResetMap(before.custom, afterSnapshot?.custom)\n await setCustomFieldsIfAny({\n dataEngine,\n entityId: E.planner.planner_availability_rule_set,\n recordId: ruleSet.id,\n tenantId: ruleSet.tenantId,\n organizationId: ruleSet.organizationId,\n values: reset,\n })\n }\n\n await emitCrudUndoSideEffects({\n dataEngine,\n action: 'updated',\n entity: ruleSet,\n identifiers: {\n id: ruleSet.id,\n organizationId: ruleSet.organizationId,\n tenantId: ruleSet.tenantId,\n },\n events: plannerAvailabilityRuleSetCrudEvents,\n indexer: availabilityRuleSetCrudIndexer,\n })\n },\n}\n\nconst deleteAvailabilityRuleSetCommand: CommandHandler<{ id?: string }, { ruleSetId: string }> = {\n id: 'planner.availability-rule-sets.delete',\n async prepare(input, ctx) {\n const id = input?.id\n if (!id) throw new CrudHttpError(400, { error: 'Availability rule set id is required.' })\n const em = (ctx.container.resolve('em') as EntityManager)\n const snapshot = await loadAvailabilityRuleSetSnapshot(em, id, commandActorScope(ctx))\n if (!snapshot) return {}\n return { before: snapshot }\n },\n async execute(input, ctx) {\n const id = input?.id\n if (!id) throw new CrudHttpError(400, { error: 'Availability rule set id is required.' })\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const scope = commandActorScope(ctx)\n const record = await findOneWithDecryption(\n em,\n PlannerAvailabilityRuleSet,\n applyScopeToWhere<PlannerAvailabilityRuleSet>({ id, deletedAt: null }, scope),\n undefined,\n scopeForDecryption(scope),\n )\n if (!record) throw new CrudHttpError(404, { error: 'Planner availability rule set not found.' })\n ensureTenantScope(ctx, record.tenantId)\n ensureOrganizationScope(ctx, record.organizationId)\n\n record.deletedAt = new Date()\n record.updatedAt = new Date()\n await em.flush()\n\n const dataEngine = (ctx.container.resolve('dataEngine') as DataEngine)\n await emitCrudSideEffects({\n dataEngine,\n action: 'deleted',\n entity: record,\n identifiers: {\n id: record.id,\n organizationId: record.organizationId,\n tenantId: record.tenantId,\n },\n events: plannerAvailabilityRuleSetCrudEvents,\n indexer: availabilityRuleSetCrudIndexer,\n })\n return { ruleSetId: record.id }\n },\n buildLog: async ({ snapshots }) => {\n const before = snapshots.before as AvailabilityRuleSetSnapshot | undefined\n if (!before) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('planner.audit.availabilityRuleSets.delete', 'Delete availability schedule'),\n resourceKind: 'planner.availabilityRuleSet',\n resourceId: before.id,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n snapshotBefore: before,\n payload: {\n undo: {\n before,\n } satisfies AvailabilityRuleSetUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<AvailabilityRuleSetUndoPayload>(logEntry)\n const before = payload?.before ?? (logEntry?.snapshotBefore as AvailabilityRuleSetSnapshot | null | undefined)\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n let ruleSet = await em.findOne(PlannerAvailabilityRuleSet, { id: before.id })\n if (!ruleSet) {\n ruleSet = em.create(PlannerAvailabilityRuleSet, {\n id: before.id,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n name: before.name,\n description: before.description ?? null,\n timezone: before.timezone,\n deletedAt: null,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(ruleSet)\n } else {\n ruleSet.name = before.name\n ruleSet.description = before.description ?? null\n ruleSet.timezone = before.timezone\n ruleSet.deletedAt = null\n ruleSet.updatedAt = new Date()\n }\n await em.flush()\n\n const dataEngine = (ctx.container.resolve('dataEngine') as DataEngine)\n if (before.custom) {\n const reset = buildCustomFieldResetMap(before.custom, undefined)\n await setCustomFieldsIfAny({\n dataEngine,\n entityId: E.planner.planner_availability_rule_set,\n recordId: ruleSet.id,\n tenantId: ruleSet.tenantId,\n organizationId: ruleSet.organizationId,\n values: reset,\n })\n }\n\n await emitCrudUndoSideEffects({\n dataEngine,\n action: 'created',\n entity: ruleSet,\n identifiers: {\n id: ruleSet.id,\n organizationId: ruleSet.organizationId,\n tenantId: ruleSet.tenantId,\n },\n events: plannerAvailabilityRuleSetCrudEvents,\n indexer: availabilityRuleSetCrudIndexer,\n })\n },\n}\n\nregisterCommand(createAvailabilityRuleSetCommand)\nregisterCommand(updateAvailabilityRuleSetCommand)\nregisterCommand(deleteAvailabilityRuleSetCommand)\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -6,12 +6,21 @@ import {
|
|
|
6
6
|
plannerAvailabilityRuleCreateSchema,
|
|
7
7
|
plannerAvailabilityRuleUpdateSchema
|
|
8
8
|
} from "../data/validators.js";
|
|
9
|
-
import {
|
|
10
|
-
|
|
9
|
+
import {
|
|
10
|
+
applyScopeToWhere,
|
|
11
|
+
commandActorScope,
|
|
12
|
+
ensureOrganizationScope,
|
|
13
|
+
ensureTenantScope,
|
|
14
|
+
explicitPlannerCommandScope,
|
|
15
|
+
extractUndoPayload
|
|
16
|
+
} from "./shared.js";
|
|
11
17
|
import { resolveRedoSnapshot } from "@open-mercato/shared/lib/commands/redo";
|
|
12
18
|
const AVAILABILITY_RULE_RESOURCE_KIND = "planner.availability.rule";
|
|
13
|
-
async function loadAvailabilityRuleSnapshot(em, id) {
|
|
14
|
-
const record = await em.findOne(
|
|
19
|
+
async function loadAvailabilityRuleSnapshot(em, id, scope) {
|
|
20
|
+
const record = await em.findOne(
|
|
21
|
+
PlannerAvailabilityRule,
|
|
22
|
+
applyScopeToWhere({ id }, scope)
|
|
23
|
+
);
|
|
15
24
|
if (!record) return null;
|
|
16
25
|
return {
|
|
17
26
|
id: record.id,
|
|
@@ -92,9 +101,13 @@ const createAvailabilityRuleCommand = {
|
|
|
92
101
|
await em.flush();
|
|
93
102
|
return { ruleId: record.id };
|
|
94
103
|
},
|
|
95
|
-
captureAfter: async (
|
|
104
|
+
captureAfter: async (input, result, ctx) => {
|
|
96
105
|
const em = ctx.container.resolve("em").fork();
|
|
97
|
-
return loadAvailabilityRuleSnapshot(
|
|
106
|
+
return loadAvailabilityRuleSnapshot(
|
|
107
|
+
em,
|
|
108
|
+
result.ruleId,
|
|
109
|
+
explicitPlannerCommandScope(input.tenantId, input.organizationId)
|
|
110
|
+
);
|
|
98
111
|
},
|
|
99
112
|
buildLog: async ({ input, result, ctx, snapshots }) => {
|
|
100
113
|
const snapshot = snapshots.after;
|
|
@@ -137,13 +150,19 @@ const updateAvailabilityRuleCommand = {
|
|
|
137
150
|
async prepare(input, ctx) {
|
|
138
151
|
const parsed = plannerAvailabilityRuleUpdateSchema.parse(input);
|
|
139
152
|
const em = ctx.container.resolve("em");
|
|
140
|
-
const snapshot = await loadAvailabilityRuleSnapshot(em, parsed.id);
|
|
153
|
+
const snapshot = await loadAvailabilityRuleSnapshot(em, parsed.id, commandActorScope(ctx));
|
|
141
154
|
return snapshot ? { before: snapshot } : {};
|
|
142
155
|
},
|
|
143
156
|
async execute(input, ctx) {
|
|
144
157
|
const parsed = plannerAvailabilityRuleUpdateSchema.parse(input);
|
|
145
158
|
const em = ctx.container.resolve("em").fork();
|
|
146
|
-
const record = await em.findOne(
|
|
159
|
+
const record = await em.findOne(
|
|
160
|
+
PlannerAvailabilityRule,
|
|
161
|
+
applyScopeToWhere(
|
|
162
|
+
{ id: parsed.id, deletedAt: null },
|
|
163
|
+
commandActorScope(ctx)
|
|
164
|
+
)
|
|
165
|
+
);
|
|
147
166
|
if (!record) throw new CrudHttpError(404, { error: "Planner availability rule not found." });
|
|
148
167
|
ensureTenantScope(ctx, record.tenantId);
|
|
149
168
|
ensureOrganizationScope(ctx, record.organizationId);
|
|
@@ -172,7 +191,11 @@ const updateAvailabilityRuleCommand = {
|
|
|
172
191
|
buildLog: async ({ snapshots, input, result, ctx }) => {
|
|
173
192
|
const before = snapshots.before;
|
|
174
193
|
const em = ctx.container.resolve("em").fork();
|
|
175
|
-
const afterSnapshot = before ? await loadAvailabilityRuleSnapshot(
|
|
194
|
+
const afterSnapshot = before ? await loadAvailabilityRuleSnapshot(
|
|
195
|
+
em,
|
|
196
|
+
before.id,
|
|
197
|
+
explicitPlannerCommandScope(before.tenantId, before.organizationId)
|
|
198
|
+
) : null;
|
|
176
199
|
const { translate } = await resolveTranslations();
|
|
177
200
|
return {
|
|
178
201
|
actionLabel: translate("planner.audit.availability.update", "Update availability rule"),
|
|
@@ -205,14 +228,17 @@ const deleteAvailabilityRuleCommand = {
|
|
|
205
228
|
const id = input?.id;
|
|
206
229
|
if (!id) return {};
|
|
207
230
|
const em = ctx.container.resolve("em");
|
|
208
|
-
const snapshot = await loadAvailabilityRuleSnapshot(em, id);
|
|
231
|
+
const snapshot = await loadAvailabilityRuleSnapshot(em, id, commandActorScope(ctx));
|
|
209
232
|
return snapshot ? { before: snapshot } : {};
|
|
210
233
|
},
|
|
211
234
|
async execute(input, ctx) {
|
|
212
235
|
const id = input?.id;
|
|
213
236
|
if (!id) throw new CrudHttpError(400, { error: "Availability rule id is required." });
|
|
214
237
|
const em = ctx.container.resolve("em").fork();
|
|
215
|
-
const record = await em.findOne(
|
|
238
|
+
const record = await em.findOne(
|
|
239
|
+
PlannerAvailabilityRule,
|
|
240
|
+
applyScopeToWhere({ id, deletedAt: null }, commandActorScope(ctx))
|
|
241
|
+
);
|
|
216
242
|
if (!record) return { ruleId: id };
|
|
217
243
|
ensureTenantScope(ctx, record.tenantId);
|
|
218
244
|
ensureOrganizationScope(ctx, record.organizationId);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/planner/commands/availability.ts"],
|
|
4
|
-
"sourcesContent": ["import type { CommandHandler } from '@open-mercato/shared/lib/commands'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { PlannerAvailabilityRule } from '../data/entities'\nimport {\n plannerAvailabilityRuleCreateSchema,\n plannerAvailabilityRuleUpdateSchema,\n type PlannerAvailabilityRuleCreateInput,\n type PlannerAvailabilityRuleUpdateInput,\n} from '../data/validators'\nimport { ensureOrganizationScope, ensureTenantScope } from './shared'\nimport type { PlannerAvailabilityKind, PlannerAvailabilitySubjectType } from '../data/entities'\nimport { extractUndoPayload } from './shared'\nimport { resolveRedoSnapshot } from '@open-mercato/shared/lib/commands/redo'\n\nconst AVAILABILITY_RULE_RESOURCE_KIND = 'planner.availability.rule'\n\ntype AvailabilityRuleSnapshot = {\n id: string\n tenantId: string\n organizationId: string\n subjectType: PlannerAvailabilitySubjectType\n subjectId: string\n timezone: string\n rrule: string\n exdates: string[]\n kind: PlannerAvailabilityKind\n note: string | null\n unavailabilityReasonEntryId: string | null\n unavailabilityReasonValue: string | null\n deletedAt: Date | null\n}\n\ntype AvailabilityRuleUndoPayload = {\n before?: AvailabilityRuleSnapshot | null\n after?: AvailabilityRuleSnapshot | null\n}\n\nasync function loadAvailabilityRuleSnapshot(em: EntityManager, id: string): Promise<AvailabilityRuleSnapshot | null> {\n const record = await em.findOne(PlannerAvailabilityRule, { id })\n if (!record) return null\n return {\n id: record.id,\n tenantId: record.tenantId,\n organizationId: record.organizationId,\n subjectType: record.subjectType,\n subjectId: record.subjectId,\n timezone: record.timezone,\n rrule: record.rrule,\n exdates: [...(record.exdates ?? [])],\n kind: record.kind,\n note: record.note ?? null,\n unavailabilityReasonEntryId: record.unavailabilityReasonEntryId ?? null,\n unavailabilityReasonValue: record.unavailabilityReasonValue ?? null,\n deletedAt: record.deletedAt ?? null,\n }\n}\n\nasync function restoreAvailabilityRuleFromSnapshot(em: EntityManager, snapshot: AvailabilityRuleSnapshot): Promise<void> {\n let record = await em.findOne(PlannerAvailabilityRule, { id: snapshot.id })\n if (!record) {\n record = em.create(PlannerAvailabilityRule, {\n id: snapshot.id,\n tenantId: snapshot.tenantId,\n organizationId: snapshot.organizationId,\n subjectType: snapshot.subjectType,\n subjectId: snapshot.subjectId,\n timezone: snapshot.timezone,\n rrule: snapshot.rrule,\n exdates: snapshot.exdates ?? [],\n kind: snapshot.kind ?? 'availability',\n note: snapshot.note ?? null,\n unavailabilityReasonEntryId: snapshot.unavailabilityReasonEntryId ?? null,\n unavailabilityReasonValue: snapshot.unavailabilityReasonValue ?? null,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(record)\n } else {\n record.subjectType = snapshot.subjectType\n record.subjectId = snapshot.subjectId\n record.timezone = snapshot.timezone\n record.rrule = snapshot.rrule\n record.exdates = snapshot.exdates ?? []\n record.kind = snapshot.kind ?? 'availability'\n record.note = snapshot.note ?? null\n record.unavailabilityReasonEntryId = snapshot.unavailabilityReasonEntryId ?? null\n record.unavailabilityReasonValue = snapshot.unavailabilityReasonValue ?? null\n record.deletedAt = snapshot.deletedAt ?? null\n }\n}\n\nconst createAvailabilityRuleCommand: CommandHandler<PlannerAvailabilityRuleCreateInput, { ruleId: string }> = {\n id: 'planner.availability.create',\n async execute(input, ctx) {\n const parsed = plannerAvailabilityRuleCreateSchema.parse(input)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const now = new Date()\n const kind = parsed.kind ?? 'availability'\n const unavailabilityReasonEntryId = kind === 'unavailability' ? parsed.unavailabilityReasonEntryId ?? null : null\n const unavailabilityReasonValue = kind === 'unavailability'\n ? (parsed.unavailabilityReasonValue ?? null)\n : null\n const record = em.create(PlannerAvailabilityRule, {\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n subjectType: parsed.subjectType,\n subjectId: parsed.subjectId,\n timezone: parsed.timezone,\n rrule: parsed.rrule,\n exdates: parsed.exdates ?? [],\n kind,\n note: parsed.note ?? null,\n unavailabilityReasonEntryId,\n unavailabilityReasonValue,\n createdAt: now,\n updatedAt: now,\n })\n em.persist(record)\n await em.flush()\n return { ruleId: record.id }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n return loadAvailabilityRuleSnapshot(em, result.ruleId)\n },\n buildLog: async ({ input, result, ctx, snapshots }) => {\n const snapshot = snapshots.after as AvailabilityRuleSnapshot | undefined\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('planner.audit.availability.create', 'Create availability rule'),\n resourceKind: AVAILABILITY_RULE_RESOURCE_KIND,\n resourceId: result?.ruleId ?? null,\n tenantId: input?.tenantId ?? ctx.auth?.tenantId ?? null,\n organizationId: input?.organizationId ?? ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,\n snapshotAfter: snapshot ?? null,\n payload: {\n undo: {\n after: snapshot,\n } satisfies AvailabilityRuleUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const ruleId = logEntry?.resourceId ?? null\n if (!ruleId) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const record = await em.findOne(PlannerAvailabilityRule, { id: ruleId })\n if (record) {\n record.deletedAt = new Date()\n await em.flush()\n }\n },\n redo: async ({ logEntry, ctx }) => {\n const after = resolveRedoSnapshot<AvailabilityRuleSnapshot>(logEntry)\n if (!after) throw new CrudHttpError(400, { error: '[internal] redo snapshot unavailable for availability rule create' })\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n await restoreAvailabilityRuleFromSnapshot(em, { ...after, deletedAt: null })\n await em.flush()\n return { ruleId: after.id }\n },\n}\n\nconst updateAvailabilityRuleCommand: CommandHandler<PlannerAvailabilityRuleUpdateInput, { ruleId: string }> = {\n id: 'planner.availability.update',\n async prepare(input, ctx) {\n const parsed = plannerAvailabilityRuleUpdateSchema.parse(input)\n const em = (ctx.container.resolve('em') as EntityManager)\n const snapshot = await loadAvailabilityRuleSnapshot(em, parsed.id)\n return snapshot ? { before: snapshot } : {}\n },\n async execute(input, ctx) {\n const parsed = plannerAvailabilityRuleUpdateSchema.parse(input)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const record = await em.findOne(PlannerAvailabilityRule, { id: parsed.id, deletedAt: null })\n if (!record) throw new CrudHttpError(404, { error: 'Planner availability rule not found.' })\n ensureTenantScope(ctx, record.tenantId)\n ensureOrganizationScope(ctx, record.organizationId)\n\n if (parsed.subjectType !== undefined) record.subjectType = parsed.subjectType\n if (parsed.subjectId !== undefined) record.subjectId = parsed.subjectId\n if (parsed.timezone !== undefined) record.timezone = parsed.timezone\n if (parsed.rrule !== undefined) record.rrule = parsed.rrule\n if (parsed.exdates !== undefined) record.exdates = parsed.exdates\n if (parsed.kind !== undefined) record.kind = parsed.kind\n if (parsed.note !== undefined) record.note = parsed.note ?? null\n const nextKind = parsed.kind ?? record.kind\n if (nextKind !== 'unavailability') {\n record.unavailabilityReasonEntryId = null\n record.unavailabilityReasonValue = null\n } else {\n if (parsed.unavailabilityReasonEntryId !== undefined) {\n record.unavailabilityReasonEntryId = parsed.unavailabilityReasonEntryId ?? null\n }\n if (parsed.unavailabilityReasonValue !== undefined) {\n record.unavailabilityReasonValue = parsed.unavailabilityReasonValue ?? null\n }\n }\n\n await em.flush()\n return { ruleId: record.id }\n },\n buildLog: async ({ snapshots, input, result, ctx }) => {\n const before = snapshots.before as AvailabilityRuleSnapshot | undefined\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const afterSnapshot = before ? await loadAvailabilityRuleSnapshot(em, before.id) : null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('planner.audit.availability.update', 'Update availability rule'),\n resourceKind: AVAILABILITY_RULE_RESOURCE_KIND,\n resourceId: result?.ruleId ?? input?.id ?? null,\n tenantId: ctx.auth?.tenantId ?? null,\n organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,\n snapshotBefore: before ?? null,\n snapshotAfter: afterSnapshot ?? null,\n payload: {\n undo: {\n before: before ?? null,\n after: afterSnapshot ?? null,\n } satisfies AvailabilityRuleUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<AvailabilityRuleUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n await restoreAvailabilityRuleFromSnapshot(em, before)\n await em.flush()\n },\n}\n\nconst deleteAvailabilityRuleCommand: CommandHandler<{ id?: string }, { ruleId: string }> = {\n id: 'planner.availability.delete',\n async prepare(input, ctx) {\n const id = input?.id\n if (!id) return {}\n const em = (ctx.container.resolve('em') as EntityManager)\n const snapshot = await loadAvailabilityRuleSnapshot(em, id)\n return snapshot ? { before: snapshot } : {}\n },\n async execute(input, ctx) {\n const id = input?.id\n if (!id) throw new CrudHttpError(400, { error: 'Availability rule id is required.' })\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const record = await em.findOne(PlannerAvailabilityRule, { id, deletedAt: null })\n if (!record) return { ruleId: id }\n ensureTenantScope(ctx, record.tenantId)\n ensureOrganizationScope(ctx, record.organizationId)\n record.deletedAt = new Date()\n await em.flush()\n return { ruleId: record.id }\n },\n buildLog: async ({ snapshots, input, result, ctx }) => {\n const before = snapshots.before as AvailabilityRuleSnapshot | undefined\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('planner.audit.availability.delete', 'Delete availability rule'),\n resourceKind: AVAILABILITY_RULE_RESOURCE_KIND,\n resourceId: result?.ruleId ?? input?.id ?? null,\n tenantId: ctx.auth?.tenantId ?? null,\n organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,\n snapshotBefore: before ?? null,\n payload: {\n undo: {\n before: before ?? null,\n } satisfies AvailabilityRuleUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<AvailabilityRuleUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n await restoreAvailabilityRuleFromSnapshot(em, { ...before, deletedAt: null })\n await em.flush()\n },\n}\n\nregisterCommand(createAvailabilityRuleCommand)\nregisterCommand(updateAvailabilityRuleCommand)\nregisterCommand(deleteAvailabilityRuleCommand)\n"],
|
|
5
|
-
"mappings": "AACA,SAAS,uBAAuB;AAEhC,SAAS,qBAAqB;AAC9B,SAAS,2BAA2B;AACpC,SAAS,+BAA+B;AACxC;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AACP,
|
|
4
|
+
"sourcesContent": ["import type { CommandHandler } from '@open-mercato/shared/lib/commands'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { PlannerAvailabilityRule } from '../data/entities'\nimport {\n plannerAvailabilityRuleCreateSchema,\n plannerAvailabilityRuleUpdateSchema,\n type PlannerAvailabilityRuleCreateInput,\n type PlannerAvailabilityRuleUpdateInput,\n} from '../data/validators'\nimport {\n applyScopeToWhere,\n commandActorScope,\n ensureOrganizationScope,\n ensureTenantScope,\n explicitPlannerCommandScope,\n extractUndoPayload,\n type PlannerCommandScope,\n} from './shared'\nimport type { PlannerAvailabilityKind, PlannerAvailabilitySubjectType } from '../data/entities'\nimport { resolveRedoSnapshot } from '@open-mercato/shared/lib/commands/redo'\n\nconst AVAILABILITY_RULE_RESOURCE_KIND = 'planner.availability.rule'\n\ntype AvailabilityRuleSnapshot = {\n id: string\n tenantId: string\n organizationId: string\n subjectType: PlannerAvailabilitySubjectType\n subjectId: string\n timezone: string\n rrule: string\n exdates: string[]\n kind: PlannerAvailabilityKind\n note: string | null\n unavailabilityReasonEntryId: string | null\n unavailabilityReasonValue: string | null\n deletedAt: Date | null\n}\n\ntype AvailabilityRuleUndoPayload = {\n before?: AvailabilityRuleSnapshot | null\n after?: AvailabilityRuleSnapshot | null\n}\n\nasync function loadAvailabilityRuleSnapshot(\n em: EntityManager,\n id: string,\n scope: PlannerCommandScope,\n): Promise<AvailabilityRuleSnapshot | null> {\n const record = await em.findOne(\n PlannerAvailabilityRule,\n applyScopeToWhere<PlannerAvailabilityRule>({ id }, scope),\n )\n if (!record) return null\n return {\n id: record.id,\n tenantId: record.tenantId,\n organizationId: record.organizationId,\n subjectType: record.subjectType,\n subjectId: record.subjectId,\n timezone: record.timezone,\n rrule: record.rrule,\n exdates: [...(record.exdates ?? [])],\n kind: record.kind,\n note: record.note ?? null,\n unavailabilityReasonEntryId: record.unavailabilityReasonEntryId ?? null,\n unavailabilityReasonValue: record.unavailabilityReasonValue ?? null,\n deletedAt: record.deletedAt ?? null,\n }\n}\n\nasync function restoreAvailabilityRuleFromSnapshot(em: EntityManager, snapshot: AvailabilityRuleSnapshot): Promise<void> {\n let record = await em.findOne(PlannerAvailabilityRule, { id: snapshot.id })\n if (!record) {\n record = em.create(PlannerAvailabilityRule, {\n id: snapshot.id,\n tenantId: snapshot.tenantId,\n organizationId: snapshot.organizationId,\n subjectType: snapshot.subjectType,\n subjectId: snapshot.subjectId,\n timezone: snapshot.timezone,\n rrule: snapshot.rrule,\n exdates: snapshot.exdates ?? [],\n kind: snapshot.kind ?? 'availability',\n note: snapshot.note ?? null,\n unavailabilityReasonEntryId: snapshot.unavailabilityReasonEntryId ?? null,\n unavailabilityReasonValue: snapshot.unavailabilityReasonValue ?? null,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(record)\n } else {\n record.subjectType = snapshot.subjectType\n record.subjectId = snapshot.subjectId\n record.timezone = snapshot.timezone\n record.rrule = snapshot.rrule\n record.exdates = snapshot.exdates ?? []\n record.kind = snapshot.kind ?? 'availability'\n record.note = snapshot.note ?? null\n record.unavailabilityReasonEntryId = snapshot.unavailabilityReasonEntryId ?? null\n record.unavailabilityReasonValue = snapshot.unavailabilityReasonValue ?? null\n record.deletedAt = snapshot.deletedAt ?? null\n }\n}\n\nconst createAvailabilityRuleCommand: CommandHandler<PlannerAvailabilityRuleCreateInput, { ruleId: string }> = {\n id: 'planner.availability.create',\n async execute(input, ctx) {\n const parsed = plannerAvailabilityRuleCreateSchema.parse(input)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const now = new Date()\n const kind = parsed.kind ?? 'availability'\n const unavailabilityReasonEntryId = kind === 'unavailability' ? parsed.unavailabilityReasonEntryId ?? null : null\n const unavailabilityReasonValue = kind === 'unavailability'\n ? (parsed.unavailabilityReasonValue ?? null)\n : null\n const record = em.create(PlannerAvailabilityRule, {\n tenantId: parsed.tenantId,\n organizationId: parsed.organizationId,\n subjectType: parsed.subjectType,\n subjectId: parsed.subjectId,\n timezone: parsed.timezone,\n rrule: parsed.rrule,\n exdates: parsed.exdates ?? [],\n kind,\n note: parsed.note ?? null,\n unavailabilityReasonEntryId,\n unavailabilityReasonValue,\n createdAt: now,\n updatedAt: now,\n })\n em.persist(record)\n await em.flush()\n return { ruleId: record.id }\n },\n captureAfter: async (input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n return loadAvailabilityRuleSnapshot(\n em,\n result.ruleId,\n explicitPlannerCommandScope(input.tenantId, input.organizationId),\n )\n },\n buildLog: async ({ input, result, ctx, snapshots }) => {\n const snapshot = snapshots.after as AvailabilityRuleSnapshot | undefined\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('planner.audit.availability.create', 'Create availability rule'),\n resourceKind: AVAILABILITY_RULE_RESOURCE_KIND,\n resourceId: result?.ruleId ?? null,\n tenantId: input?.tenantId ?? ctx.auth?.tenantId ?? null,\n organizationId: input?.organizationId ?? ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,\n snapshotAfter: snapshot ?? null,\n payload: {\n undo: {\n after: snapshot,\n } satisfies AvailabilityRuleUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const ruleId = logEntry?.resourceId ?? null\n if (!ruleId) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const record = await em.findOne(PlannerAvailabilityRule, { id: ruleId })\n if (record) {\n record.deletedAt = new Date()\n await em.flush()\n }\n },\n redo: async ({ logEntry, ctx }) => {\n const after = resolveRedoSnapshot<AvailabilityRuleSnapshot>(logEntry)\n if (!after) throw new CrudHttpError(400, { error: '[internal] redo snapshot unavailable for availability rule create' })\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n await restoreAvailabilityRuleFromSnapshot(em, { ...after, deletedAt: null })\n await em.flush()\n return { ruleId: after.id }\n },\n}\n\nconst updateAvailabilityRuleCommand: CommandHandler<PlannerAvailabilityRuleUpdateInput, { ruleId: string }> = {\n id: 'planner.availability.update',\n async prepare(input, ctx) {\n const parsed = plannerAvailabilityRuleUpdateSchema.parse(input)\n const em = (ctx.container.resolve('em') as EntityManager)\n const snapshot = await loadAvailabilityRuleSnapshot(em, parsed.id, commandActorScope(ctx))\n return snapshot ? { before: snapshot } : {}\n },\n async execute(input, ctx) {\n const parsed = plannerAvailabilityRuleUpdateSchema.parse(input)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const record = await em.findOne(\n PlannerAvailabilityRule,\n applyScopeToWhere<PlannerAvailabilityRule>(\n { id: parsed.id, deletedAt: null },\n commandActorScope(ctx),\n ),\n )\n if (!record) throw new CrudHttpError(404, { error: 'Planner availability rule not found.' })\n ensureTenantScope(ctx, record.tenantId)\n ensureOrganizationScope(ctx, record.organizationId)\n\n if (parsed.subjectType !== undefined) record.subjectType = parsed.subjectType\n if (parsed.subjectId !== undefined) record.subjectId = parsed.subjectId\n if (parsed.timezone !== undefined) record.timezone = parsed.timezone\n if (parsed.rrule !== undefined) record.rrule = parsed.rrule\n if (parsed.exdates !== undefined) record.exdates = parsed.exdates\n if (parsed.kind !== undefined) record.kind = parsed.kind\n if (parsed.note !== undefined) record.note = parsed.note ?? null\n const nextKind = parsed.kind ?? record.kind\n if (nextKind !== 'unavailability') {\n record.unavailabilityReasonEntryId = null\n record.unavailabilityReasonValue = null\n } else {\n if (parsed.unavailabilityReasonEntryId !== undefined) {\n record.unavailabilityReasonEntryId = parsed.unavailabilityReasonEntryId ?? null\n }\n if (parsed.unavailabilityReasonValue !== undefined) {\n record.unavailabilityReasonValue = parsed.unavailabilityReasonValue ?? null\n }\n }\n\n await em.flush()\n return { ruleId: record.id }\n },\n buildLog: async ({ snapshots, input, result, ctx }) => {\n const before = snapshots.before as AvailabilityRuleSnapshot | undefined\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const afterSnapshot = before\n ? await loadAvailabilityRuleSnapshot(\n em,\n before.id,\n explicitPlannerCommandScope(before.tenantId, before.organizationId),\n )\n : null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('planner.audit.availability.update', 'Update availability rule'),\n resourceKind: AVAILABILITY_RULE_RESOURCE_KIND,\n resourceId: result?.ruleId ?? input?.id ?? null,\n tenantId: ctx.auth?.tenantId ?? null,\n organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,\n snapshotBefore: before ?? null,\n snapshotAfter: afterSnapshot ?? null,\n payload: {\n undo: {\n before: before ?? null,\n after: afterSnapshot ?? null,\n } satisfies AvailabilityRuleUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<AvailabilityRuleUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n await restoreAvailabilityRuleFromSnapshot(em, before)\n await em.flush()\n },\n}\n\nconst deleteAvailabilityRuleCommand: CommandHandler<{ id?: string }, { ruleId: string }> = {\n id: 'planner.availability.delete',\n async prepare(input, ctx) {\n const id = input?.id\n if (!id) return {}\n const em = (ctx.container.resolve('em') as EntityManager)\n const snapshot = await loadAvailabilityRuleSnapshot(em, id, commandActorScope(ctx))\n return snapshot ? { before: snapshot } : {}\n },\n async execute(input, ctx) {\n const id = input?.id\n if (!id) throw new CrudHttpError(400, { error: 'Availability rule id is required.' })\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const record = await em.findOne(\n PlannerAvailabilityRule,\n applyScopeToWhere<PlannerAvailabilityRule>({ id, deletedAt: null }, commandActorScope(ctx)),\n )\n if (!record) return { ruleId: id }\n ensureTenantScope(ctx, record.tenantId)\n ensureOrganizationScope(ctx, record.organizationId)\n record.deletedAt = new Date()\n await em.flush()\n return { ruleId: record.id }\n },\n buildLog: async ({ snapshots, input, result, ctx }) => {\n const before = snapshots.before as AvailabilityRuleSnapshot | undefined\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('planner.audit.availability.delete', 'Delete availability rule'),\n resourceKind: AVAILABILITY_RULE_RESOURCE_KIND,\n resourceId: result?.ruleId ?? input?.id ?? null,\n tenantId: ctx.auth?.tenantId ?? null,\n organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,\n snapshotBefore: before ?? null,\n payload: {\n undo: {\n before: before ?? null,\n } satisfies AvailabilityRuleUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<AvailabilityRuleUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n await restoreAvailabilityRuleFromSnapshot(em, { ...before, deletedAt: null })\n await em.flush()\n },\n}\n\nregisterCommand(createAvailabilityRuleCommand)\nregisterCommand(updateAvailabilityRuleCommand)\nregisterCommand(deleteAvailabilityRuleCommand)\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,8 +1,41 @@
|
|
|
1
1
|
import { ensureOrganizationScope, ensureTenantScope } from "@open-mercato/shared/lib/commands/scope";
|
|
2
2
|
import { extractUndoPayload } from "@open-mercato/shared/lib/commands/undo";
|
|
3
|
+
function commandActorScope(ctx) {
|
|
4
|
+
const isPrivilegedActor = ctx.auth?.isSuperAdmin === true || ctx.systemActor === true;
|
|
5
|
+
const tenantId = isPrivilegedActor ? null : ctx.auth?.tenantId ?? ctx.organizationScope?.tenantId ?? null;
|
|
6
|
+
const organizationId = isPrivilegedActor ? null : ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null;
|
|
7
|
+
const organizationUnrestricted = isPrivilegedActor || organizationId === null && tenantId !== null && ctx.organizationScope?.allowedIds === null;
|
|
8
|
+
return {
|
|
9
|
+
tenantId,
|
|
10
|
+
organizationId: organizationUnrestricted ? null : organizationId,
|
|
11
|
+
requireTenant: !isPrivilegedActor,
|
|
12
|
+
requireOrganization: !organizationUnrestricted
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function explicitPlannerCommandScope(tenantId, organizationId) {
|
|
16
|
+
return {
|
|
17
|
+
tenantId,
|
|
18
|
+
organizationId,
|
|
19
|
+
requireTenant: true,
|
|
20
|
+
requireOrganization: true
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function applyScopeToWhere(where, scope) {
|
|
24
|
+
const scoped = { ...where };
|
|
25
|
+
if (scope.requireTenant || scope.tenantId !== null) scoped.tenantId = scope.tenantId;
|
|
26
|
+
if (scope.requireOrganization || scope.organizationId !== null) scoped.organizationId = scope.organizationId;
|
|
27
|
+
return scoped;
|
|
28
|
+
}
|
|
29
|
+
function scopeForDecryption(scope) {
|
|
30
|
+
return { tenantId: scope.tenantId, organizationId: scope.organizationId };
|
|
31
|
+
}
|
|
3
32
|
export {
|
|
33
|
+
applyScopeToWhere,
|
|
34
|
+
commandActorScope,
|
|
4
35
|
ensureOrganizationScope,
|
|
5
36
|
ensureTenantScope,
|
|
6
|
-
|
|
37
|
+
explicitPlannerCommandScope,
|
|
38
|
+
extractUndoPayload,
|
|
39
|
+
scopeForDecryption
|
|
7
40
|
};
|
|
8
41
|
//# sourceMappingURL=shared.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/planner/commands/shared.ts"],
|
|
4
|
-
"sourcesContent": ["import { ensureOrganizationScope, ensureTenantScope } from '@open-mercato/shared/lib/commands/scope'\nimport { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'\n\nexport { ensureOrganizationScope, ensureTenantScope, extractUndoPayload }\n"],
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { ensureOrganizationScope, ensureTenantScope } from '@open-mercato/shared/lib/commands/scope'\nimport { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'\nimport type { FilterQuery } from '@mikro-orm/postgresql'\n\nexport { ensureOrganizationScope, ensureTenantScope, extractUndoPayload }\n\nexport type PlannerCommandScope = {\n tenantId: string | null\n organizationId: string | null\n requireTenant: boolean\n requireOrganization: boolean\n}\n\nexport function commandActorScope(ctx: CommandRuntimeContext): PlannerCommandScope {\n const isPrivilegedActor = ctx.auth?.isSuperAdmin === true || ctx.systemActor === true\n const tenantId = isPrivilegedActor ? null : (ctx.auth?.tenantId ?? ctx.organizationScope?.tenantId ?? null)\n const organizationId = isPrivilegedActor ? null : (ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null)\n const organizationUnrestricted =\n isPrivilegedActor || (organizationId === null && tenantId !== null && ctx.organizationScope?.allowedIds === null)\n return {\n tenantId,\n organizationId: organizationUnrestricted ? null : organizationId,\n requireTenant: !isPrivilegedActor,\n requireOrganization: !organizationUnrestricted,\n }\n}\n\nexport function explicitPlannerCommandScope(\n tenantId: string | null,\n organizationId: string | null,\n): PlannerCommandScope {\n return {\n tenantId,\n organizationId,\n requireTenant: true,\n requireOrganization: true,\n }\n}\n\nexport function applyScopeToWhere<TEntity extends object>(\n where: FilterQuery<TEntity>,\n scope: PlannerCommandScope,\n): FilterQuery<TEntity> {\n const scoped = { ...(where as Record<string, unknown>) }\n if (scope.requireTenant || scope.tenantId !== null) scoped.tenantId = scope.tenantId\n if (scope.requireOrganization || scope.organizationId !== null) scoped.organizationId = scope.organizationId\n return scoped as FilterQuery<TEntity>\n}\n\nexport function scopeForDecryption(\n scope: PlannerCommandScope,\n): { tenantId: string | null; organizationId: string | null } {\n return { tenantId: scope.tenantId, organizationId: scope.organizationId }\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,yBAAyB,yBAAyB;AAC3D,SAAS,0BAA0B;AAY5B,SAAS,kBAAkB,KAAiD;AACjF,QAAM,oBAAoB,IAAI,MAAM,iBAAiB,QAAQ,IAAI,gBAAgB;AACjF,QAAM,WAAW,oBAAoB,OAAQ,IAAI,MAAM,YAAY,IAAI,mBAAmB,YAAY;AACtG,QAAM,iBAAiB,oBAAoB,OAAQ,IAAI,0BAA0B,IAAI,MAAM,SAAS;AACpG,QAAM,2BACJ,qBAAsB,mBAAmB,QAAQ,aAAa,QAAQ,IAAI,mBAAmB,eAAe;AAC9G,SAAO;AAAA,IACL;AAAA,IACA,gBAAgB,2BAA2B,OAAO;AAAA,IAClD,eAAe,CAAC;AAAA,IAChB,qBAAqB,CAAC;AAAA,EACxB;AACF;AAEO,SAAS,4BACd,UACA,gBACqB;AACrB,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,eAAe;AAAA,IACf,qBAAqB;AAAA,EACvB;AACF;AAEO,SAAS,kBACd,OACA,OACsB;AACtB,QAAM,SAAS,EAAE,GAAI,MAAkC;AACvD,MAAI,MAAM,iBAAiB,MAAM,aAAa,KAAM,QAAO,WAAW,MAAM;AAC5E,MAAI,MAAM,uBAAuB,MAAM,mBAAmB,KAAM,QAAO,iBAAiB,MAAM;AAC9F,SAAO;AACT;AAEO,SAAS,mBACd,OAC4D;AAC5D,SAAO,EAAE,UAAU,MAAM,UAAU,gBAAgB,MAAM,eAAe;AAC1E;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|