@open-mercato/core 0.5.1-develop.2975.ccbadc8198 → 0.5.1-develop.2996.ce62fd491c
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 +2 -2
- package/dist/generated/entities/sidebar_variant/index.js +25 -0
- package/dist/generated/entities/sidebar_variant/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +1 -0
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +13 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/helpers/integration/authUi.js +1 -1
- package/dist/helpers/integration/authUi.js.map +2 -2
- package/dist/modules/audit_logs/services/actionLogService.js +4 -5
- package/dist/modules/audit_logs/services/actionLogService.js.map +2 -2
- package/dist/modules/auth/api/sidebar/preferences/route.js +224 -35
- package/dist/modules/auth/api/sidebar/preferences/route.js.map +3 -3
- package/dist/modules/auth/api/sidebar/variants/[id]/route.js +161 -0
- package/dist/modules/auth/api/sidebar/variants/[id]/route.js.map +7 -0
- package/dist/modules/auth/api/sidebar/variants/route.js +142 -0
- package/dist/modules/auth/api/sidebar/variants/route.js.map +7 -0
- package/dist/modules/auth/backend/sidebar-customization/page.js +16 -0
- package/dist/modules/auth/backend/sidebar-customization/page.js.map +7 -0
- package/dist/modules/auth/backend/sidebar-customization/page.meta.js +28 -0
- package/dist/modules/auth/backend/sidebar-customization/page.meta.js.map +7 -0
- package/dist/modules/auth/data/entities.js +45 -4
- package/dist/modules/auth/data/entities.js.map +2 -2
- package/dist/modules/auth/data/validators.js +63 -1
- package/dist/modules/auth/data/validators.js.map +2 -2
- package/dist/modules/auth/migrations/Migration20260427081815.js +15 -0
- package/dist/modules/auth/migrations/Migration20260427081815.js.map +7 -0
- package/dist/modules/auth/migrations/Migration20260427124900.js +15 -0
- package/dist/modules/auth/migrations/Migration20260427124900.js.map +7 -0
- package/dist/modules/auth/migrations/Migration20260427143311.js +72 -0
- package/dist/modules/auth/migrations/Migration20260427143311.js.map +7 -0
- package/dist/modules/auth/services/sidebarPreferencesService.js +176 -16
- package/dist/modules/auth/services/sidebarPreferencesService.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies/[id]/page.js +3 -1
- package/dist/modules/customers/backend/customers/companies/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +4 -2
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +8 -3
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/components/detail/CompanyPeopleSection.js +3 -2
- package/dist/modules/customers/components/detail/CompanyPeopleSection.js.map +2 -2
- package/dist/modules/customers/components/formConfig.js +3 -3
- package/dist/modules/customers/components/formConfig.js.map +2 -2
- package/dist/modules/customers/lib/displayName.js +12 -0
- package/dist/modules/customers/lib/displayName.js.map +2 -2
- package/dist/modules/entities/cli.js +5 -6
- package/dist/modules/entities/cli.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/reset-password/page.js +124 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/reset-password/page.js.map +7 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/reset-password/page.meta.js +11 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/reset-password/page.meta.js.map +7 -0
- package/generated/entities/sidebar_variant/index.ts +11 -0
- package/generated/entities.ids.generated.ts +1 -0
- package/generated/entity-fields-registry.ts +13 -0
- package/package.json +6 -6
- package/src/helpers/integration/authUi.ts +1 -1
- package/src/modules/audit_logs/services/actionLogService.ts +5 -6
- package/src/modules/auth/api/sidebar/preferences/route.ts +266 -34
- package/src/modules/auth/api/sidebar/variants/[id]/route.ts +183 -0
- package/src/modules/auth/api/sidebar/variants/route.ts +157 -0
- package/src/modules/auth/backend/sidebar-customization/page.meta.ts +34 -0
- package/src/modules/auth/backend/sidebar-customization/page.tsx +17 -0
- package/src/modules/auth/data/entities.ts +48 -2
- package/src/modules/auth/data/validators.ts +70 -0
- package/src/modules/auth/migrations/.snapshot-open-mercato.json +790 -71
- package/src/modules/auth/migrations/Migration20260427081815.ts +16 -0
- package/src/modules/auth/migrations/Migration20260427124900.ts +19 -0
- package/src/modules/auth/migrations/Migration20260427143311.ts +83 -0
- package/src/modules/auth/services/sidebarPreferencesService.ts +243 -18
- package/src/modules/customers/backend/customers/companies/[id]/page.tsx +5 -4
- package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +6 -5
- package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +13 -9
- package/src/modules/customers/components/detail/CompanyPeopleSection.tsx +3 -2
- package/src/modules/customers/components/formConfig.tsx +3 -3
- package/src/modules/customers/lib/displayName.ts +21 -0
- package/src/modules/entities/cli.ts +5 -6
- package/src/modules/portal/frontend/[orgSlug]/portal/reset-password/page.meta.ts +9 -0
- package/src/modules/portal/frontend/[orgSlug]/portal/reset-password/page.tsx +168 -0
- package/src/modules/portal/i18n/de.json +20 -0
- package/src/modules/portal/i18n/en.json +20 -0
- package/src/modules/portal/i18n/es.json +20 -0
- package/src/modules/portal/i18n/pl.json +20 -0
|
@@ -2,7 +2,11 @@ import { NextResponse } from "next/server";
|
|
|
2
2
|
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
3
3
|
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
4
4
|
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
5
|
-
import {
|
|
5
|
+
import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
6
|
+
import {
|
|
7
|
+
sidebarPreferencesInputSchema,
|
|
8
|
+
sidebarPreferencesScopeSchema
|
|
9
|
+
} from "../../../data/validators.js";
|
|
6
10
|
import {
|
|
7
11
|
loadRoleSidebarPreferences,
|
|
8
12
|
loadSidebarPreference,
|
|
@@ -14,14 +18,16 @@ import { Role, RoleSidebarPreference } from "../../../data/entities.js";
|
|
|
14
18
|
import { z } from "zod";
|
|
15
19
|
const metadata = {
|
|
16
20
|
GET: { requireAuth: true },
|
|
17
|
-
PUT: { requireAuth: true }
|
|
21
|
+
PUT: { requireAuth: true },
|
|
22
|
+
DELETE: { requireAuth: true }
|
|
18
23
|
};
|
|
19
24
|
const sidebarSettingsSchema = z.object({
|
|
20
25
|
version: z.number().int().positive(),
|
|
21
26
|
groupOrder: z.array(z.string()),
|
|
22
27
|
groupLabels: z.record(z.string(), z.string()),
|
|
23
28
|
itemLabels: z.record(z.string(), z.string()),
|
|
24
|
-
hiddenItems: z.array(z.string())
|
|
29
|
+
hiddenItems: z.array(z.string()),
|
|
30
|
+
itemOrder: z.record(z.string(), z.array(z.string()))
|
|
25
31
|
});
|
|
26
32
|
const sidebarRoleEntrySchema = z.object({
|
|
27
33
|
id: z.string().uuid(),
|
|
@@ -32,27 +38,109 @@ const sidebarPreferencesResponseSchema = z.object({
|
|
|
32
38
|
locale: z.string(),
|
|
33
39
|
settings: sidebarSettingsSchema,
|
|
34
40
|
canApplyToRoles: z.boolean(),
|
|
35
|
-
roles: z.array(sidebarRoleEntrySchema)
|
|
41
|
+
roles: z.array(sidebarRoleEntrySchema),
|
|
42
|
+
scope: sidebarPreferencesScopeSchema
|
|
36
43
|
});
|
|
37
44
|
const sidebarPreferencesUpdateResponseSchema = sidebarPreferencesResponseSchema.extend({
|
|
38
45
|
appliedRoles: z.array(z.string().uuid()),
|
|
39
46
|
clearedRoles: z.array(z.string().uuid())
|
|
40
47
|
});
|
|
48
|
+
const sidebarPreferencesDeleteResponseSchema = z.object({
|
|
49
|
+
ok: z.literal(true),
|
|
50
|
+
scope: sidebarPreferencesScopeSchema
|
|
51
|
+
});
|
|
41
52
|
const sidebarErrorSchema = z.object({
|
|
42
53
|
error: z.string()
|
|
43
54
|
});
|
|
55
|
+
const FEATURE_MANAGE = "auth.sidebar.manage";
|
|
56
|
+
function emptySettings() {
|
|
57
|
+
return {
|
|
58
|
+
version: SIDEBAR_PREFERENCES_VERSION,
|
|
59
|
+
groupOrder: [],
|
|
60
|
+
groupLabels: {},
|
|
61
|
+
itemLabels: {},
|
|
62
|
+
hiddenItems: [],
|
|
63
|
+
itemOrder: {}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
async function loadRolesPayload(em, options) {
|
|
67
|
+
const roleScope = options.tenantId ? { $or: [{ tenantId: options.tenantId }, { tenantId: null }] } : { tenantId: null };
|
|
68
|
+
const roles = await findWithDecryption(
|
|
69
|
+
em,
|
|
70
|
+
Role,
|
|
71
|
+
roleScope,
|
|
72
|
+
{ orderBy: { name: "asc" } },
|
|
73
|
+
{ tenantId: options.tenantId, organizationId: null }
|
|
74
|
+
);
|
|
75
|
+
if (roles.length === 0) return [];
|
|
76
|
+
const rolePrefs = await loadRoleSidebarPreferences(em, {
|
|
77
|
+
roleIds: roles.map((r) => r.id),
|
|
78
|
+
tenantId: options.tenantId,
|
|
79
|
+
locale: options.locale
|
|
80
|
+
});
|
|
81
|
+
return roles.map((role) => ({
|
|
82
|
+
id: role.id,
|
|
83
|
+
name: role.name,
|
|
84
|
+
hasPreference: rolePrefs.has(role.id)
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
async function findRoleInScope(em, options) {
|
|
88
|
+
const role = await findOneWithDecryption(
|
|
89
|
+
em,
|
|
90
|
+
Role,
|
|
91
|
+
{ id: options.roleId },
|
|
92
|
+
void 0,
|
|
93
|
+
{ tenantId: options.tenantId, organizationId: null }
|
|
94
|
+
);
|
|
95
|
+
if (!role) return null;
|
|
96
|
+
if (role.tenantId && options.tenantId && role.tenantId !== options.tenantId) return null;
|
|
97
|
+
if (role.tenantId && !options.tenantId) return null;
|
|
98
|
+
return role;
|
|
99
|
+
}
|
|
44
100
|
async function GET(req) {
|
|
45
101
|
const auth = await getAuthFromRequest(req);
|
|
46
102
|
if (!auth) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
103
|
+
const url = new URL(req.url);
|
|
104
|
+
const roleIdParam = url.searchParams.get("roleId");
|
|
47
105
|
const { locale } = await resolveTranslations();
|
|
48
106
|
const { resolve } = await createRequestContainer();
|
|
49
107
|
const em = resolve("em");
|
|
50
108
|
const rbac = resolve("rbacService");
|
|
51
109
|
const canApplyToRoles = await rbac.userHasAllFeatures?.(
|
|
52
110
|
auth.sub,
|
|
53
|
-
[
|
|
111
|
+
[FEATURE_MANAGE],
|
|
54
112
|
{ tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null }
|
|
55
113
|
) ?? false;
|
|
114
|
+
if (roleIdParam) {
|
|
115
|
+
if (!canApplyToRoles) {
|
|
116
|
+
return NextResponse.json({ error: "Forbidden", requiredFeatures: [FEATURE_MANAGE] }, { status: 403 });
|
|
117
|
+
}
|
|
118
|
+
const role = await findRoleInScope(em, { roleId: roleIdParam, tenantId: auth.tenantId ?? null });
|
|
119
|
+
if (!role) {
|
|
120
|
+
return NextResponse.json({ error: "Role not found" }, { status: 404 });
|
|
121
|
+
}
|
|
122
|
+
const rolePrefs = await loadRoleSidebarPreferences(em, {
|
|
123
|
+
roleIds: [role.id],
|
|
124
|
+
tenantId: auth.tenantId ?? null,
|
|
125
|
+
locale
|
|
126
|
+
});
|
|
127
|
+
const pref = rolePrefs.get(role.id) ?? null;
|
|
128
|
+
const rolesPayload2 = await loadRolesPayload(em, { tenantId: auth.tenantId ?? null, locale });
|
|
129
|
+
return NextResponse.json({
|
|
130
|
+
locale,
|
|
131
|
+
settings: pref ? {
|
|
132
|
+
version: pref.version ?? SIDEBAR_PREFERENCES_VERSION,
|
|
133
|
+
groupOrder: pref.groupOrder ?? [],
|
|
134
|
+
groupLabels: pref.groupLabels ?? {},
|
|
135
|
+
itemLabels: pref.itemLabels ?? {},
|
|
136
|
+
hiddenItems: pref.hiddenItems ?? [],
|
|
137
|
+
itemOrder: pref.itemOrder ?? {}
|
|
138
|
+
} : emptySettings(),
|
|
139
|
+
canApplyToRoles,
|
|
140
|
+
roles: rolesPayload2,
|
|
141
|
+
scope: { type: "role", roleId: role.id }
|
|
142
|
+
});
|
|
143
|
+
}
|
|
56
144
|
const effectiveUserId = auth.isApiKey ? auth.userId : auth.sub;
|
|
57
145
|
const settings = effectiveUserId ? await loadSidebarPreference(em, {
|
|
58
146
|
userId: effectiveUserId,
|
|
@@ -60,21 +148,7 @@ async function GET(req) {
|
|
|
60
148
|
organizationId: auth.orgId ?? null,
|
|
61
149
|
locale
|
|
62
150
|
}) : null;
|
|
63
|
-
|
|
64
|
-
if (canApplyToRoles) {
|
|
65
|
-
const roleScope = auth.tenantId ? { $or: [{ tenantId: auth.tenantId }, { tenantId: null }] } : { tenantId: null };
|
|
66
|
-
const roles = await em.find(Role, roleScope, { orderBy: { name: "asc" } });
|
|
67
|
-
const rolePrefs = await loadRoleSidebarPreferences(em, {
|
|
68
|
-
roleIds: roles.map((r) => r.id),
|
|
69
|
-
tenantId: auth.tenantId ?? null,
|
|
70
|
-
locale
|
|
71
|
-
});
|
|
72
|
-
rolesPayload = roles.map((role) => ({
|
|
73
|
-
id: role.id,
|
|
74
|
-
name: role.name,
|
|
75
|
-
hasPreference: rolePrefs.has(role.id)
|
|
76
|
-
}));
|
|
77
|
-
}
|
|
151
|
+
const rolesPayload = canApplyToRoles ? await loadRolesPayload(em, { tenantId: auth.tenantId ?? null, locale }) : [];
|
|
78
152
|
return NextResponse.json({
|
|
79
153
|
locale,
|
|
80
154
|
settings: {
|
|
@@ -82,10 +156,12 @@ async function GET(req) {
|
|
|
82
156
|
groupOrder: settings?.groupOrder ?? [],
|
|
83
157
|
groupLabels: settings?.groupLabels ?? {},
|
|
84
158
|
itemLabels: settings?.itemLabels ?? {},
|
|
85
|
-
hiddenItems: settings?.hiddenItems ?? []
|
|
159
|
+
hiddenItems: settings?.hiddenItems ?? [],
|
|
160
|
+
itemOrder: settings?.itemOrder ?? {}
|
|
86
161
|
},
|
|
87
162
|
canApplyToRoles,
|
|
88
|
-
roles: rolesPayload
|
|
163
|
+
roles: rolesPayload,
|
|
164
|
+
scope: { type: "user" }
|
|
89
165
|
});
|
|
90
166
|
}
|
|
91
167
|
async function PUT(req) {
|
|
@@ -141,6 +217,24 @@ async function PUT(req) {
|
|
|
141
217
|
values.push(trimmed);
|
|
142
218
|
}
|
|
143
219
|
return values;
|
|
220
|
+
})(),
|
|
221
|
+
itemOrder: (() => {
|
|
222
|
+
const source = parsed.data.itemOrder ?? {};
|
|
223
|
+
const out = {};
|
|
224
|
+
for (const [groupKey, list] of Object.entries(source)) {
|
|
225
|
+
const trimmedGroup = groupKey.trim();
|
|
226
|
+
if (!trimmedGroup) continue;
|
|
227
|
+
const seenItem = /* @__PURE__ */ new Set();
|
|
228
|
+
const values = [];
|
|
229
|
+
for (const itemKey of list) {
|
|
230
|
+
const trimmedItem = itemKey.trim();
|
|
231
|
+
if (!trimmedItem || seenItem.has(trimmedItem)) continue;
|
|
232
|
+
seenItem.add(trimmedItem);
|
|
233
|
+
values.push(trimmedItem);
|
|
234
|
+
}
|
|
235
|
+
if (values.length > 0) out[trimmedGroup] = values;
|
|
236
|
+
}
|
|
237
|
+
return out;
|
|
144
238
|
})()
|
|
145
239
|
};
|
|
146
240
|
const { locale } = await resolveTranslations();
|
|
@@ -148,17 +242,55 @@ async function PUT(req) {
|
|
|
148
242
|
const em = container.resolve("em");
|
|
149
243
|
const rbac = container.resolve("rbacService");
|
|
150
244
|
const cache = container.resolve("cache");
|
|
151
|
-
const applyToRolesSource = parsed.data.applyToRoles ?? [];
|
|
152
|
-
const applyToRoles = Array.from(new Set(applyToRolesSource.map((id) => id.trim()).filter((id) => id.length > 0)));
|
|
153
|
-
const clearRoleIdsSource = parsed.data.clearRoleIds ?? [];
|
|
154
|
-
const clearRoleIds = Array.from(new Set(clearRoleIdsSource.map((id) => id.trim()).filter((id) => id.length > 0)));
|
|
155
245
|
const canApplyToRoles = await rbac.userHasAllFeatures?.(
|
|
156
246
|
auth.sub,
|
|
157
|
-
[
|
|
247
|
+
[FEATURE_MANAGE],
|
|
158
248
|
{ tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null }
|
|
159
249
|
) ?? false;
|
|
250
|
+
const scope = parsed.data.scope ?? { type: "user" };
|
|
251
|
+
if (scope.type === "role") {
|
|
252
|
+
if (!canApplyToRoles) {
|
|
253
|
+
return NextResponse.json({ error: "Forbidden", requiredFeatures: [FEATURE_MANAGE] }, { status: 403 });
|
|
254
|
+
}
|
|
255
|
+
const role = await findRoleInScope(em, { roleId: scope.roleId, tenantId: auth.tenantId ?? null });
|
|
256
|
+
if (!role) {
|
|
257
|
+
return NextResponse.json({ error: "Role not found" }, { status: 404 });
|
|
258
|
+
}
|
|
259
|
+
const saved = await saveRoleSidebarPreference(em, {
|
|
260
|
+
roleId: role.id,
|
|
261
|
+
tenantId: auth.tenantId ?? null,
|
|
262
|
+
locale
|
|
263
|
+
}, payload);
|
|
264
|
+
if (cache?.deleteByTags) {
|
|
265
|
+
try {
|
|
266
|
+
await cache.deleteByTags([`nav:sidebar:role:${role.id}`]);
|
|
267
|
+
} catch {
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
const rolesPayload2 = await loadRolesPayload(em, { tenantId: auth.tenantId ?? null, locale });
|
|
271
|
+
return NextResponse.json({
|
|
272
|
+
locale,
|
|
273
|
+
settings: {
|
|
274
|
+
version: saved?.version ?? payload.version,
|
|
275
|
+
groupOrder: saved?.groupOrder ?? payload.groupOrder,
|
|
276
|
+
groupLabels: saved?.groupLabels ?? payload.groupLabels,
|
|
277
|
+
itemLabels: saved?.itemLabels ?? payload.itemLabels,
|
|
278
|
+
hiddenItems: saved?.hiddenItems ?? payload.hiddenItems,
|
|
279
|
+
itemOrder: saved?.itemOrder ?? payload.itemOrder
|
|
280
|
+
},
|
|
281
|
+
canApplyToRoles,
|
|
282
|
+
roles: rolesPayload2,
|
|
283
|
+
scope: { type: "role", roleId: role.id },
|
|
284
|
+
appliedRoles: [],
|
|
285
|
+
clearedRoles: []
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
const applyToRolesSource = parsed.data.applyToRoles ?? [];
|
|
289
|
+
const applyToRoles = Array.from(new Set(applyToRolesSource.map((id) => id.trim()).filter((id) => id.length > 0)));
|
|
290
|
+
const clearRoleIdsSource = parsed.data.clearRoleIds ?? [];
|
|
291
|
+
const clearRoleIds = Array.from(new Set(clearRoleIdsSource.map((id) => id.trim()).filter((id) => id.length > 0)));
|
|
160
292
|
if ((applyToRoles.length > 0 || clearRoleIds.length > 0) && !canApplyToRoles) {
|
|
161
|
-
return NextResponse.json({ error: "Forbidden", requiredFeatures: [
|
|
293
|
+
return NextResponse.json({ error: "Forbidden", requiredFeatures: [FEATURE_MANAGE] }, { status: 403 });
|
|
162
294
|
}
|
|
163
295
|
const settings = await saveSidebarPreference(em, {
|
|
164
296
|
userId: effectiveUserId,
|
|
@@ -167,9 +299,15 @@ async function PUT(req) {
|
|
|
167
299
|
locale
|
|
168
300
|
}, payload);
|
|
169
301
|
const roleScope = auth.tenantId ? { $or: [{ tenantId: auth.tenantId }, { tenantId: null }] } : { tenantId: null };
|
|
170
|
-
const availableRoles = canApplyToRoles ? await
|
|
302
|
+
const availableRoles = canApplyToRoles ? await findWithDecryption(
|
|
303
|
+
em,
|
|
304
|
+
Role,
|
|
305
|
+
roleScope,
|
|
306
|
+
{ orderBy: { name: "asc" } },
|
|
307
|
+
{ tenantId: auth.tenantId ?? null, organizationId: null }
|
|
308
|
+
) : [];
|
|
171
309
|
const roleMap = new Map(availableRoles.map((role) => [String(role.id), role]));
|
|
172
|
-
|
|
310
|
+
const updatedRoleIds = [];
|
|
173
311
|
if (applyToRoles.length > 0) {
|
|
174
312
|
const missing = applyToRoles.filter((id) => !roleMap.has(id));
|
|
175
313
|
if (missing.length) {
|
|
@@ -189,7 +327,6 @@ async function PUT(req) {
|
|
|
189
327
|
if (filteredClearRoleIds.length > 0) {
|
|
190
328
|
await em.nativeDelete(RoleSidebarPreference, {
|
|
191
329
|
role: { $in: filteredClearRoleIds },
|
|
192
|
-
locale,
|
|
193
330
|
tenantId: auth.tenantId ?? null
|
|
194
331
|
});
|
|
195
332
|
if (cache?.deleteByTags) {
|
|
@@ -228,25 +365,64 @@ async function PUT(req) {
|
|
|
228
365
|
settings,
|
|
229
366
|
canApplyToRoles,
|
|
230
367
|
roles: rolesPayload,
|
|
368
|
+
scope: { type: "user" },
|
|
231
369
|
appliedRoles: updatedRoleIds,
|
|
232
370
|
clearedRoles: filteredClearRoleIds
|
|
233
371
|
});
|
|
234
372
|
}
|
|
373
|
+
async function DELETE(req) {
|
|
374
|
+
const auth = await getAuthFromRequest(req);
|
|
375
|
+
if (!auth) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
376
|
+
const url = new URL(req.url);
|
|
377
|
+
const roleIdParam = url.searchParams.get("roleId");
|
|
378
|
+
if (!roleIdParam) {
|
|
379
|
+
return NextResponse.json({ error: "roleId query parameter is required" }, { status: 400 });
|
|
380
|
+
}
|
|
381
|
+
const container = await createRequestContainer();
|
|
382
|
+
const em = container.resolve("em");
|
|
383
|
+
const rbac = container.resolve("rbacService");
|
|
384
|
+
const cache = container.resolve("cache");
|
|
385
|
+
const canApplyToRoles = await rbac.userHasAllFeatures?.(
|
|
386
|
+
auth.sub,
|
|
387
|
+
[FEATURE_MANAGE],
|
|
388
|
+
{ tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null }
|
|
389
|
+
) ?? false;
|
|
390
|
+
if (!canApplyToRoles) {
|
|
391
|
+
return NextResponse.json({ error: "Forbidden", requiredFeatures: [FEATURE_MANAGE] }, { status: 403 });
|
|
392
|
+
}
|
|
393
|
+
const role = await findRoleInScope(em, { roleId: roleIdParam, tenantId: auth.tenantId ?? null });
|
|
394
|
+
if (!role) {
|
|
395
|
+
return NextResponse.json({ error: "Role not found" }, { status: 404 });
|
|
396
|
+
}
|
|
397
|
+
await em.nativeDelete(RoleSidebarPreference, {
|
|
398
|
+
role: role.id,
|
|
399
|
+
tenantId: auth.tenantId ?? null
|
|
400
|
+
});
|
|
401
|
+
if (cache?.deleteByTags) {
|
|
402
|
+
try {
|
|
403
|
+
await cache.deleteByTags([`nav:sidebar:role:${role.id}`]);
|
|
404
|
+
} catch {
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return NextResponse.json({ ok: true, scope: { type: "role", roleId: role.id } });
|
|
408
|
+
}
|
|
235
409
|
const openApi = {
|
|
236
410
|
tag: "Authentication & Accounts",
|
|
237
411
|
summary: "Sidebar preferences",
|
|
238
412
|
methods: {
|
|
239
413
|
GET: {
|
|
240
414
|
summary: "Get sidebar preferences",
|
|
241
|
-
description: "Returns
|
|
415
|
+
description: "Returns sidebar customization for the current user (default) or the specified role (`?roleId=\u2026`, requires `auth.sidebar.manage`).",
|
|
242
416
|
responses: [
|
|
243
417
|
{ status: 200, description: "Current sidebar configuration", schema: sidebarPreferencesResponseSchema },
|
|
244
|
-
{ status: 401, description: "Unauthorized", schema: sidebarErrorSchema }
|
|
418
|
+
{ status: 401, description: "Unauthorized", schema: sidebarErrorSchema },
|
|
419
|
+
{ status: 403, description: "Missing features for role-scope read", schema: sidebarErrorSchema },
|
|
420
|
+
{ status: 404, description: "Role not found in current tenant scope", schema: sidebarErrorSchema }
|
|
245
421
|
]
|
|
246
422
|
},
|
|
247
423
|
PUT: {
|
|
248
424
|
summary: "Update sidebar preferences",
|
|
249
|
-
description:
|
|
425
|
+
description: 'Updates sidebar configuration. With `scope.type === "user"` (default) writes the calling user\'s personal preferences and may optionally apply the same settings to selected roles via `applyToRoles[]`. With `scope.type === "role"` writes the named role variant directly (requires `auth.sidebar.manage`); `applyToRoles[]` and `clearRoleIds[]` are rejected in this mode.',
|
|
250
426
|
requestBody: {
|
|
251
427
|
contentType: "application/json",
|
|
252
428
|
schema: sidebarPreferencesInputSchema
|
|
@@ -255,12 +431,25 @@ const openApi = {
|
|
|
255
431
|
{ status: 200, description: "Preferences saved", schema: sidebarPreferencesUpdateResponseSchema },
|
|
256
432
|
{ status: 400, description: "Invalid payload", schema: sidebarErrorSchema },
|
|
257
433
|
{ status: 401, description: "Unauthorized", schema: sidebarErrorSchema },
|
|
258
|
-
{ status: 403, description: "Missing features for role-wide updates", schema: sidebarErrorSchema }
|
|
434
|
+
{ status: 403, description: "Missing features for role-wide updates", schema: sidebarErrorSchema },
|
|
435
|
+
{ status: 404, description: "Role not found in current tenant scope", schema: sidebarErrorSchema }
|
|
436
|
+
]
|
|
437
|
+
},
|
|
438
|
+
DELETE: {
|
|
439
|
+
summary: "Delete a role sidebar variant",
|
|
440
|
+
description: "Removes the role variant for the current tenant + locale. Idempotent. Requires `auth.sidebar.manage`.",
|
|
441
|
+
responses: [
|
|
442
|
+
{ status: 200, description: "Variant deleted (or never existed)", schema: sidebarPreferencesDeleteResponseSchema },
|
|
443
|
+
{ status: 400, description: "Missing roleId query parameter", schema: sidebarErrorSchema },
|
|
444
|
+
{ status: 401, description: "Unauthorized", schema: sidebarErrorSchema },
|
|
445
|
+
{ status: 403, description: "Missing features", schema: sidebarErrorSchema },
|
|
446
|
+
{ status: 404, description: "Role not found in current tenant scope", schema: sidebarErrorSchema }
|
|
259
447
|
]
|
|
260
448
|
}
|
|
261
449
|
}
|
|
262
450
|
};
|
|
263
451
|
export {
|
|
452
|
+
DELETE,
|
|
264
453
|
GET,
|
|
265
454
|
PUT,
|
|
266
455
|
metadata,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/auth/api/sidebar/preferences/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { sidebarPreferencesInputSchema } from '../../../data/validators'\nimport {\n loadRoleSidebarPreferences,\n loadSidebarPreference,\n saveRoleSidebarPreference,\n saveSidebarPreference,\n} from '../../../services/sidebarPreferencesService'\nimport { SIDEBAR_PREFERENCES_VERSION } from '@open-mercato/shared/modules/navigation/sidebarPreferences'\nimport { Role, RoleSidebarPreference } from '../../../data/entities'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { z } from 'zod'\n\nexport const metadata = {\n GET: { requireAuth: true },\n PUT: { requireAuth: true },\n}\n\nconst sidebarSettingsSchema = z.object({\n version: z.number().int().positive(),\n groupOrder: z.array(z.string()),\n groupLabels: z.record(z.string(), z.string()),\n itemLabels: z.record(z.string(), z.string()),\n hiddenItems: z.array(z.string()),\n})\n\nconst sidebarRoleEntrySchema = z.object({\n id: z.string().uuid(),\n name: z.string(),\n hasPreference: z.boolean(),\n})\n\nconst sidebarPreferencesResponseSchema = z.object({\n locale: z.string(),\n settings: sidebarSettingsSchema,\n canApplyToRoles: z.boolean(),\n roles: z.array(sidebarRoleEntrySchema),\n})\n\nconst sidebarPreferencesUpdateResponseSchema = sidebarPreferencesResponseSchema.extend({\n appliedRoles: z.array(z.string().uuid()),\n clearedRoles: z.array(z.string().uuid()),\n})\n\nconst sidebarErrorSchema = z.object({\n error: z.string(),\n})\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const { locale } = await resolveTranslations()\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as any\n const rbac = resolve('rbacService') as any\n\n const canApplyToRoles = await rbac.userHasAllFeatures?.(\n auth.sub,\n ['auth.sidebar.manage'],\n { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },\n ) ?? false\n\n // For API key auth, use userId (the actual user) if available\n const effectiveUserId = auth.isApiKey ? auth.userId : auth.sub\n const settings = effectiveUserId\n ? await loadSidebarPreference(em, {\n userId: effectiveUserId,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n locale,\n })\n : null\n\n let rolesPayload: Array<{ id: string; name: string; hasPreference: boolean }> = []\n if (canApplyToRoles) {\n const roleScope = auth.tenantId\n ? { $or: [{ tenantId: auth.tenantId }, { tenantId: null }] }\n : { tenantId: null }\n const roles = await em.find(Role, roleScope as any, { orderBy: { name: 'asc' } })\n const rolePrefs = await loadRoleSidebarPreferences(em, {\n roleIds: roles.map((r: Role) => r.id),\n tenantId: auth.tenantId ?? null,\n locale,\n })\n rolesPayload = roles.map((role: Role) => ({\n id: role.id,\n name: role.name,\n hasPreference: rolePrefs.has(role.id),\n }))\n }\n\n return NextResponse.json({\n locale,\n settings: {\n version: settings?.version ?? SIDEBAR_PREFERENCES_VERSION,\n groupOrder: settings?.groupOrder ?? [],\n groupLabels: settings?.groupLabels ?? {},\n itemLabels: settings?.itemLabels ?? {},\n hiddenItems: settings?.hiddenItems ?? [],\n },\n canApplyToRoles,\n roles: rolesPayload,\n })\n}\n\nexport async function PUT(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n // For API key auth, use userId (the actual user) if available\n const effectiveUserId = auth.isApiKey ? auth.userId : auth.sub\n if (!effectiveUserId) {\n return NextResponse.json({ error: 'Cannot save preferences: no user associated with this API key' }, { status: 403 })\n }\n\n let parsedBody: unknown\n try {\n parsedBody = await req.json()\n } catch {\n return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 })\n }\n\n const parsed = sidebarPreferencesInputSchema.safeParse(parsedBody)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 400 })\n }\n\n const sanitizeRecord = (record?: Record<string, string>) => {\n if (!record) return {}\n const result: Record<string, string> = {}\n for (const [key, value] of Object.entries(record)) {\n const trimmedKey = key.trim()\n const trimmedValue = value.trim()\n if (!trimmedKey || !trimmedValue) continue\n result[trimmedKey] = trimmedValue\n }\n return result\n }\n\n const groupOrderSource = parsed.data.groupOrder ?? []\n const seen = new Set<string>()\n const groupOrder: string[] = []\n for (const id of groupOrderSource) {\n const trimmed = id.trim()\n if (!trimmed || seen.has(trimmed)) continue\n seen.add(trimmed)\n groupOrder.push(trimmed)\n }\n\n const payload = {\n version: parsed.data.version ?? SIDEBAR_PREFERENCES_VERSION,\n groupOrder,\n groupLabels: sanitizeRecord(parsed.data.groupLabels),\n itemLabels: sanitizeRecord(parsed.data.itemLabels),\n hiddenItems: (() => {\n const source = parsed.data.hiddenItems ?? []\n const seenHidden = new Set<string>()\n const values: string[] = []\n for (const href of source) {\n const trimmed = href.trim()\n if (!trimmed || seenHidden.has(trimmed)) continue\n seenHidden.add(trimmed)\n values.push(trimmed)\n }\n return values\n })(),\n }\n\n const { locale } = await resolveTranslations()\n const container = await createRequestContainer()\n const em = container.resolve('em') as any\n const rbac = container.resolve('rbacService') as any\n const cache = container.resolve('cache') as { deleteByTags?: (tags: string[]) => Promise<unknown> } | undefined\n\n const applyToRolesSource = parsed.data.applyToRoles ?? []\n const applyToRoles = Array.from(new Set(applyToRolesSource.map((id) => id.trim()).filter((id) => id.length > 0)))\n const clearRoleIdsSource = parsed.data.clearRoleIds ?? []\n const clearRoleIds = Array.from(new Set(clearRoleIdsSource.map((id) => id.trim()).filter((id) => id.length > 0)))\n\n const canApplyToRoles = await rbac.userHasAllFeatures?.(\n auth.sub,\n ['auth.sidebar.manage'],\n { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },\n ) ?? false\n\n if ((applyToRoles.length > 0 || clearRoleIds.length > 0) && !canApplyToRoles) {\n return NextResponse.json({ error: 'Forbidden', requiredFeatures: ['auth.sidebar.manage'] }, { status: 403 })\n }\n\n const settings = await saveSidebarPreference(em, {\n userId: effectiveUserId,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n locale,\n }, payload)\n\n const roleScope = auth.tenantId\n ? { $or: [{ tenantId: auth.tenantId }, { tenantId: null }] }\n : { tenantId: null }\n const availableRoles = canApplyToRoles\n ? await em.find(Role, roleScope as any, { orderBy: { name: 'asc' } })\n : []\n const roleMap = new Map<string, Role>(availableRoles.map((role: Role) => [String(role.id), role]))\n\n let updatedRoleIds: string[] = []\n if (applyToRoles.length > 0) {\n const missing = applyToRoles.filter((id) => !roleMap.has(id))\n if (missing.length) {\n return NextResponse.json({ error: 'Invalid roles', missing }, { status: 400 })\n }\n for (const roleId of applyToRoles) {\n const role = roleMap.get(roleId)!\n await saveRoleSidebarPreference(em, {\n roleId: role.id,\n tenantId: auth.tenantId ?? null,\n locale,\n }, payload)\n updatedRoleIds.push(role.id)\n }\n }\n\n const filteredClearRoleIds = clearRoleIds.filter((id) => !updatedRoleIds.includes(id) && !applyToRoles.includes(id))\n\n if (filteredClearRoleIds.length > 0) {\n await em.nativeDelete(RoleSidebarPreference, {\n role: { $in: filteredClearRoleIds },\n locale,\n tenantId: auth.tenantId ?? null,\n })\n if (cache?.deleteByTags) {\n try {\n await cache.deleteByTags(filteredClearRoleIds.map((roleId) => `nav:sidebar:role:${roleId}`))\n } catch {}\n }\n }\n\n if (cache?.deleteByTags) {\n const tags = [\n `nav:sidebar:user:${auth.sub}`,\n `nav:sidebar:scope:${auth.sub}:${auth.tenantId ?? 'null'}:${auth.orgId ?? 'null'}:${locale}`,\n ...updatedRoleIds.map((roleId) => `nav:sidebar:role:${roleId}`),\n ]\n try {\n await cache.deleteByTags(tags)\n } catch {}\n }\n\n let rolesPayload: Array<{ id: string; name: string; hasPreference: boolean }> = []\n if (canApplyToRoles) {\n const rolePrefs = await loadRoleSidebarPreferences(em, {\n roleIds: availableRoles.map((role: Role) => role.id),\n tenantId: auth.tenantId ?? null,\n locale,\n })\n rolesPayload = availableRoles.map((role: Role) => ({\n id: role.id,\n name: role.name,\n hasPreference: rolePrefs.has(role.id),\n }))\n }\n\n return NextResponse.json({\n locale,\n settings,\n canApplyToRoles,\n roles: rolesPayload,\n appliedRoles: updatedRoleIds,\n clearedRoles: filteredClearRoleIds,\n })\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Authentication & Accounts',\n summary: 'Sidebar preferences',\n methods: {\n GET: {\n summary: 'Get sidebar preferences',\n description: 'Returns personal sidebar customization and any role-level preferences the user can manage.',\n responses: [\n { status: 200, description: 'Current sidebar configuration', schema: sidebarPreferencesResponseSchema },\n { status: 401, description: 'Unauthorized', schema: sidebarErrorSchema },\n ],\n },\n PUT: {\n summary: 'Update sidebar preferences',\n description: 'Updates personal sidebar configuration and, optionally, applies the same settings to selected roles.',\n requestBody: {\n contentType: 'application/json',\n schema: sidebarPreferencesInputSchema,\n },\n responses: [\n { status: 200, description: 'Preferences saved', schema: sidebarPreferencesUpdateResponseSchema },\n { status: 400, description: 'Invalid payload', schema: sidebarErrorSchema },\n { status: 401, description: 'Unauthorized', schema: sidebarErrorSchema },\n { status: 403, description: 'Missing features for role-wide updates', schema: sidebarErrorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": []
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport {\n sidebarPreferencesInputSchema,\n sidebarPreferencesScopeSchema,\n} from '../../../data/validators'\nimport {\n loadRoleSidebarPreferences,\n loadSidebarPreference,\n saveRoleSidebarPreference,\n saveSidebarPreference,\n} from '../../../services/sidebarPreferencesService'\nimport { SIDEBAR_PREFERENCES_VERSION } from '@open-mercato/shared/modules/navigation/sidebarPreferences'\nimport { Role, RoleSidebarPreference } from '../../../data/entities'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { z } from 'zod'\n\nexport const metadata = {\n GET: { requireAuth: true },\n PUT: { requireAuth: true },\n DELETE: { requireAuth: true },\n}\n\nconst sidebarSettingsSchema = z.object({\n version: z.number().int().positive(),\n groupOrder: z.array(z.string()),\n groupLabels: z.record(z.string(), z.string()),\n itemLabels: z.record(z.string(), z.string()),\n hiddenItems: z.array(z.string()),\n itemOrder: z.record(z.string(), z.array(z.string())),\n})\n\nconst sidebarRoleEntrySchema = z.object({\n id: z.string().uuid(),\n name: z.string(),\n hasPreference: z.boolean(),\n})\n\nconst sidebarPreferencesResponseSchema = z.object({\n locale: z.string(),\n settings: sidebarSettingsSchema,\n canApplyToRoles: z.boolean(),\n roles: z.array(sidebarRoleEntrySchema),\n scope: sidebarPreferencesScopeSchema,\n})\n\nconst sidebarPreferencesUpdateResponseSchema = sidebarPreferencesResponseSchema.extend({\n appliedRoles: z.array(z.string().uuid()),\n clearedRoles: z.array(z.string().uuid()),\n})\n\nconst sidebarPreferencesDeleteResponseSchema = z.object({\n ok: z.literal(true),\n scope: sidebarPreferencesScopeSchema,\n})\n\nconst sidebarErrorSchema = z.object({\n error: z.string(),\n})\n\nconst FEATURE_MANAGE = 'auth.sidebar.manage'\n\ntype EmptySettings = {\n version: number\n groupOrder: string[]\n groupLabels: Record<string, string>\n itemLabels: Record<string, string>\n hiddenItems: string[]\n itemOrder: Record<string, string[]>\n}\n\nfunction emptySettings(): EmptySettings {\n return {\n version: SIDEBAR_PREFERENCES_VERSION,\n groupOrder: [],\n groupLabels: {},\n itemLabels: {},\n hiddenItems: [],\n itemOrder: {},\n }\n}\n\nasync function loadRolesPayload(\n em: EntityManager,\n options: { tenantId: string | null; locale: string },\n): Promise<Array<{ id: string; name: string; hasPreference: boolean }>> {\n const roleScope: FilterQuery<Role> = options.tenantId\n ? { $or: [{ tenantId: options.tenantId }, { tenantId: null }] }\n : { tenantId: null }\n const roles = await findWithDecryption(\n em,\n Role,\n roleScope,\n { orderBy: { name: 'asc' } },\n { tenantId: options.tenantId, organizationId: null },\n )\n if (roles.length === 0) return []\n const rolePrefs = await loadRoleSidebarPreferences(em, {\n roleIds: roles.map((r: Role) => r.id),\n tenantId: options.tenantId,\n locale: options.locale,\n })\n return roles.map((role: Role) => ({\n id: role.id,\n name: role.name,\n hasPreference: rolePrefs.has(role.id),\n }))\n}\n\nasync function findRoleInScope(\n em: EntityManager,\n options: { roleId: string; tenantId: string | null },\n): Promise<Role | null> {\n const role = await findOneWithDecryption(\n em,\n Role,\n { id: options.roleId },\n undefined,\n { tenantId: options.tenantId, organizationId: null },\n )\n if (!role) return null\n // Cross-tenant guard: a role belongs to either the auth tenant or the global (null tenant) pool.\n // Reject the lookup otherwise so a multi-tenant deployment can't leak across tenants.\n if (role.tenantId && options.tenantId && role.tenantId !== options.tenantId) return null\n if (role.tenantId && !options.tenantId) return null\n return role\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const url = new URL(req.url)\n const roleIdParam = url.searchParams.get('roleId')\n\n const { locale } = await resolveTranslations()\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as EntityManager\n const rbac = resolve('rbacService') as any\n\n const canApplyToRoles = await rbac.userHasAllFeatures?.(\n auth.sub,\n [FEATURE_MANAGE],\n { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },\n ) ?? false\n\n // Role-scoped read: requires `auth.sidebar.manage`.\n if (roleIdParam) {\n if (!canApplyToRoles) {\n return NextResponse.json({ error: 'Forbidden', requiredFeatures: [FEATURE_MANAGE] }, { status: 403 })\n }\n const role = await findRoleInScope(em, { roleId: roleIdParam, tenantId: auth.tenantId ?? null })\n if (!role) {\n return NextResponse.json({ error: 'Role not found' }, { status: 404 })\n }\n const rolePrefs = await loadRoleSidebarPreferences(em, {\n roleIds: [role.id],\n tenantId: auth.tenantId ?? null,\n locale,\n })\n const pref = rolePrefs.get(role.id) ?? null\n const rolesPayload = await loadRolesPayload(em, { tenantId: auth.tenantId ?? null, locale })\n return NextResponse.json({\n locale,\n settings: pref\n ? {\n version: pref.version ?? SIDEBAR_PREFERENCES_VERSION,\n groupOrder: pref.groupOrder ?? [],\n groupLabels: pref.groupLabels ?? {},\n itemLabels: pref.itemLabels ?? {},\n hiddenItems: pref.hiddenItems ?? [],\n itemOrder: pref.itemOrder ?? {},\n }\n : emptySettings(),\n canApplyToRoles,\n roles: rolesPayload,\n scope: { type: 'role', roleId: role.id },\n })\n }\n\n // For API key auth, use userId (the actual user) if available\n const effectiveUserId = auth.isApiKey ? auth.userId : auth.sub\n const settings = effectiveUserId\n ? await loadSidebarPreference(em, {\n userId: effectiveUserId,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n locale,\n })\n : null\n\n const rolesPayload = canApplyToRoles\n ? await loadRolesPayload(em, { tenantId: auth.tenantId ?? null, locale })\n : []\n\n return NextResponse.json({\n locale,\n settings: {\n version: settings?.version ?? SIDEBAR_PREFERENCES_VERSION,\n groupOrder: settings?.groupOrder ?? [],\n groupLabels: settings?.groupLabels ?? {},\n itemLabels: settings?.itemLabels ?? {},\n hiddenItems: settings?.hiddenItems ?? [],\n itemOrder: settings?.itemOrder ?? {},\n },\n canApplyToRoles,\n roles: rolesPayload,\n scope: { type: 'user' },\n })\n}\n\nexport async function PUT(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n // For API key auth, use userId (the actual user) if available\n const effectiveUserId = auth.isApiKey ? auth.userId : auth.sub\n if (!effectiveUserId) {\n return NextResponse.json({ error: 'Cannot save preferences: no user associated with this API key' }, { status: 403 })\n }\n\n let parsedBody: unknown\n try {\n parsedBody = await req.json()\n } catch {\n return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 })\n }\n\n const parsed = sidebarPreferencesInputSchema.safeParse(parsedBody)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 400 })\n }\n\n const sanitizeRecord = (record?: Record<string, string>) => {\n if (!record) return {}\n const result: Record<string, string> = {}\n for (const [key, value] of Object.entries(record)) {\n const trimmedKey = key.trim()\n const trimmedValue = value.trim()\n if (!trimmedKey || !trimmedValue) continue\n result[trimmedKey] = trimmedValue\n }\n return result\n }\n\n const groupOrderSource = parsed.data.groupOrder ?? []\n const seen = new Set<string>()\n const groupOrder: string[] = []\n for (const id of groupOrderSource) {\n const trimmed = id.trim()\n if (!trimmed || seen.has(trimmed)) continue\n seen.add(trimmed)\n groupOrder.push(trimmed)\n }\n\n const payload = {\n version: parsed.data.version ?? SIDEBAR_PREFERENCES_VERSION,\n groupOrder,\n groupLabels: sanitizeRecord(parsed.data.groupLabels),\n itemLabels: sanitizeRecord(parsed.data.itemLabels),\n hiddenItems: (() => {\n const source = parsed.data.hiddenItems ?? []\n const seenHidden = new Set<string>()\n const values: string[] = []\n for (const href of source) {\n const trimmed = href.trim()\n if (!trimmed || seenHidden.has(trimmed)) continue\n seenHidden.add(trimmed)\n values.push(trimmed)\n }\n return values\n })(),\n itemOrder: (() => {\n const source = parsed.data.itemOrder ?? {}\n const out: Record<string, string[]> = {}\n for (const [groupKey, list] of Object.entries(source)) {\n const trimmedGroup = groupKey.trim()\n if (!trimmedGroup) continue\n const seenItem = new Set<string>()\n const values: string[] = []\n for (const itemKey of list) {\n const trimmedItem = itemKey.trim()\n if (!trimmedItem || seenItem.has(trimmedItem)) continue\n seenItem.add(trimmedItem)\n values.push(trimmedItem)\n }\n if (values.length > 0) out[trimmedGroup] = values\n }\n return out\n })(),\n }\n\n const { locale } = await resolveTranslations()\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const rbac = container.resolve('rbacService') as any\n const cache = container.resolve('cache') as { deleteByTags?: (tags: string[]) => Promise<unknown> } | undefined\n\n const canApplyToRoles = await rbac.userHasAllFeatures?.(\n auth.sub,\n [FEATURE_MANAGE],\n { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },\n ) ?? false\n\n const scope = parsed.data.scope ?? { type: 'user' as const }\n\n // Role-scoped write: requires `auth.sidebar.manage` and a role visible to this tenant.\n // applyToRoles/clearRoleIds are forbidden in role scope (validator already rejects them).\n if (scope.type === 'role') {\n if (!canApplyToRoles) {\n return NextResponse.json({ error: 'Forbidden', requiredFeatures: [FEATURE_MANAGE] }, { status: 403 })\n }\n const role = await findRoleInScope(em, { roleId: scope.roleId, tenantId: auth.tenantId ?? null })\n if (!role) {\n return NextResponse.json({ error: 'Role not found' }, { status: 404 })\n }\n const saved = await saveRoleSidebarPreference(em, {\n roleId: role.id,\n tenantId: auth.tenantId ?? null,\n locale,\n }, payload)\n if (cache?.deleteByTags) {\n try {\n await cache.deleteByTags([`nav:sidebar:role:${role.id}`])\n } catch {}\n }\n const rolesPayload = await loadRolesPayload(em, { tenantId: auth.tenantId ?? null, locale })\n return NextResponse.json({\n locale,\n settings: {\n version: saved?.version ?? payload.version,\n groupOrder: saved?.groupOrder ?? payload.groupOrder,\n groupLabels: saved?.groupLabels ?? payload.groupLabels,\n itemLabels: saved?.itemLabels ?? payload.itemLabels,\n hiddenItems: saved?.hiddenItems ?? payload.hiddenItems,\n itemOrder: saved?.itemOrder ?? payload.itemOrder,\n },\n canApplyToRoles,\n roles: rolesPayload,\n scope: { type: 'role', roleId: role.id },\n appliedRoles: [],\n clearedRoles: [],\n })\n }\n\n const applyToRolesSource = parsed.data.applyToRoles ?? []\n const applyToRoles = Array.from(new Set(applyToRolesSource.map((id) => id.trim()).filter((id) => id.length > 0)))\n const clearRoleIdsSource = parsed.data.clearRoleIds ?? []\n const clearRoleIds = Array.from(new Set(clearRoleIdsSource.map((id) => id.trim()).filter((id) => id.length > 0)))\n\n if ((applyToRoles.length > 0 || clearRoleIds.length > 0) && !canApplyToRoles) {\n return NextResponse.json({ error: 'Forbidden', requiredFeatures: [FEATURE_MANAGE] }, { status: 403 })\n }\n\n const settings = await saveSidebarPreference(em, {\n userId: effectiveUserId,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n locale,\n }, payload)\n\n const roleScope: FilterQuery<Role> = auth.tenantId\n ? { $or: [{ tenantId: auth.tenantId }, { tenantId: null }] }\n : { tenantId: null }\n const availableRoles = canApplyToRoles\n ? await findWithDecryption(\n em,\n Role,\n roleScope,\n { orderBy: { name: 'asc' } },\n { tenantId: auth.tenantId ?? null, organizationId: null },\n )\n : []\n const roleMap = new Map<string, Role>(availableRoles.map((role: Role) => [String(role.id), role]))\n\n const updatedRoleIds: string[] = []\n if (applyToRoles.length > 0) {\n const missing = applyToRoles.filter((id) => !roleMap.has(id))\n if (missing.length) {\n return NextResponse.json({ error: 'Invalid roles', missing }, { status: 400 })\n }\n for (const roleId of applyToRoles) {\n const role = roleMap.get(roleId)!\n await saveRoleSidebarPreference(em, {\n roleId: role.id,\n tenantId: auth.tenantId ?? null,\n locale,\n }, payload)\n updatedRoleIds.push(role.id)\n }\n }\n\n const filteredClearRoleIds = clearRoleIds.filter((id) => !updatedRoleIds.includes(id) && !applyToRoles.includes(id))\n\n if (filteredClearRoleIds.length > 0) {\n // Cross-locale: role preferences are unique per (role, tenantId); keep the delete\n // filter aligned with save/load helpers so a clear from one locale does not leave\n // a row created under another locale orphaned.\n await em.nativeDelete(RoleSidebarPreference, {\n role: { $in: filteredClearRoleIds },\n tenantId: auth.tenantId ?? null,\n })\n if (cache?.deleteByTags) {\n try {\n await cache.deleteByTags(filteredClearRoleIds.map((roleId) => `nav:sidebar:role:${roleId}`))\n } catch {}\n }\n }\n\n if (cache?.deleteByTags) {\n const tags = [\n `nav:sidebar:user:${auth.sub}`,\n `nav:sidebar:scope:${auth.sub}:${auth.tenantId ?? 'null'}:${auth.orgId ?? 'null'}:${locale}`,\n ...updatedRoleIds.map((roleId) => `nav:sidebar:role:${roleId}`),\n ]\n try {\n await cache.deleteByTags(tags)\n } catch {}\n }\n\n let rolesPayload: Array<{ id: string; name: string; hasPreference: boolean }> = []\n if (canApplyToRoles) {\n const rolePrefs = await loadRoleSidebarPreferences(em, {\n roleIds: availableRoles.map((role: Role) => role.id),\n tenantId: auth.tenantId ?? null,\n locale,\n })\n rolesPayload = availableRoles.map((role: Role) => ({\n id: role.id,\n name: role.name,\n hasPreference: rolePrefs.has(role.id),\n }))\n }\n\n return NextResponse.json({\n locale,\n settings,\n canApplyToRoles,\n roles: rolesPayload,\n scope: { type: 'user' },\n appliedRoles: updatedRoleIds,\n clearedRoles: filteredClearRoleIds,\n })\n}\n\nexport async function DELETE(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const url = new URL(req.url)\n const roleIdParam = url.searchParams.get('roleId')\n if (!roleIdParam) {\n return NextResponse.json({ error: 'roleId query parameter is required' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const rbac = container.resolve('rbacService') as any\n const cache = container.resolve('cache') as { deleteByTags?: (tags: string[]) => Promise<unknown> } | undefined\n\n const canApplyToRoles = await rbac.userHasAllFeatures?.(\n auth.sub,\n [FEATURE_MANAGE],\n { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },\n ) ?? false\n if (!canApplyToRoles) {\n return NextResponse.json({ error: 'Forbidden', requiredFeatures: [FEATURE_MANAGE] }, { status: 403 })\n }\n\n const role = await findRoleInScope(em, { roleId: roleIdParam, tenantId: auth.tenantId ?? null })\n if (!role) {\n return NextResponse.json({ error: 'Role not found' }, { status: 404 })\n }\n\n // Cross-locale: keep the delete filter aligned with save/load helpers (no locale).\n await em.nativeDelete(RoleSidebarPreference, {\n role: role.id,\n tenantId: auth.tenantId ?? null,\n })\n\n if (cache?.deleteByTags) {\n try {\n await cache.deleteByTags([`nav:sidebar:role:${role.id}`])\n } catch {}\n }\n\n return NextResponse.json({ ok: true, scope: { type: 'role', roleId: role.id } })\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Authentication & Accounts',\n summary: 'Sidebar preferences',\n methods: {\n GET: {\n summary: 'Get sidebar preferences',\n description: 'Returns sidebar customization for the current user (default) or the specified role (`?roleId=\u2026`, requires `auth.sidebar.manage`).',\n responses: [\n { status: 200, description: 'Current sidebar configuration', schema: sidebarPreferencesResponseSchema },\n { status: 401, description: 'Unauthorized', schema: sidebarErrorSchema },\n { status: 403, description: 'Missing features for role-scope read', schema: sidebarErrorSchema },\n { status: 404, description: 'Role not found in current tenant scope', schema: sidebarErrorSchema },\n ],\n },\n PUT: {\n summary: 'Update sidebar preferences',\n description: 'Updates sidebar configuration. With `scope.type === \"user\"` (default) writes the calling user\\'s personal preferences and may optionally apply the same settings to selected roles via `applyToRoles[]`. With `scope.type === \"role\"` writes the named role variant directly (requires `auth.sidebar.manage`); `applyToRoles[]` and `clearRoleIds[]` are rejected in this mode.',\n requestBody: {\n contentType: 'application/json',\n schema: sidebarPreferencesInputSchema,\n },\n responses: [\n { status: 200, description: 'Preferences saved', schema: sidebarPreferencesUpdateResponseSchema },\n { status: 400, description: 'Invalid payload', schema: sidebarErrorSchema },\n { status: 401, description: 'Unauthorized', schema: sidebarErrorSchema },\n { status: 403, description: 'Missing features for role-wide updates', schema: sidebarErrorSchema },\n { status: 404, description: 'Role not found in current tenant scope', schema: sidebarErrorSchema },\n ],\n },\n DELETE: {\n summary: 'Delete a role sidebar variant',\n description: 'Removes the role variant for the current tenant + locale. Idempotent. Requires `auth.sidebar.manage`.',\n responses: [\n { status: 200, description: 'Variant deleted (or never existed)', schema: sidebarPreferencesDeleteResponseSchema },\n { status: 400, description: 'Missing roleId query parameter', schema: sidebarErrorSchema },\n { status: 401, description: 'Unauthorized', schema: sidebarErrorSchema },\n { status: 403, description: 'Missing features', schema: sidebarErrorSchema },\n { status: 404, description: 'Role not found in current tenant scope', schema: sidebarErrorSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["rolesPayload"]
|
|
7
7
|
}
|