@open-mercato/core 0.6.7-develop.6775.1.c2313bb8a3 → 0.6.7-develop.6785.1.1dd7cfac55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +3 -3
- package/agentic/standalone-guide.md +13 -0
- package/dist/generated/entities/organization/index.js +2 -0
- package/dist/generated/entities/organization/index.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +1 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/auth/cli.js +1 -1
- package/dist/modules/auth/cli.js.map +2 -2
- package/dist/modules/auth/lib/backendChrome.js +4 -6
- package/dist/modules/auth/lib/backendChrome.js.map +2 -2
- package/dist/modules/auth/lib/setup-app.js +1 -0
- package/dist/modules/auth/lib/setup-app.js.map +2 -2
- package/dist/modules/auth/services/rbacService.js +29 -44
- package/dist/modules/auth/services/rbacService.js.map +2 -2
- package/dist/modules/catalog/widgets/injection/product-seo/widget.js +1 -1
- package/dist/modules/catalog/widgets/injection/product-seo/widget.js.map +2 -2
- package/dist/modules/communication_channels/lib/access-control.js +2 -2
- package/dist/modules/communication_channels/lib/access-control.js.map +2 -2
- package/dist/modules/customer_accounts/api/invitations/accept.js +1 -2
- package/dist/modules/customer_accounts/api/invitations/accept.js.map +2 -2
- package/dist/modules/customer_accounts/api/login.js +4 -2
- package/dist/modules/customer_accounts/api/login.js.map +2 -2
- package/dist/modules/customer_accounts/api/magic-link/verify.js +4 -2
- package/dist/modules/customer_accounts/api/magic-link/verify.js.map +2 -2
- package/dist/modules/customer_accounts/api/portal/feature-check.js +5 -4
- package/dist/modules/customer_accounts/api/portal/feature-check.js.map +2 -2
- package/dist/modules/customer_accounts/api/portal/nav.js +6 -4
- package/dist/modules/customer_accounts/api/portal/nav.js.map +2 -2
- package/dist/modules/customer_accounts/api/portal/profile.js +5 -1
- package/dist/modules/customer_accounts/api/portal/profile.js.map +2 -2
- package/dist/modules/customer_accounts/api/portal/sessions-refresh.js +3 -3
- package/dist/modules/customer_accounts/api/portal/sessions-refresh.js.map +2 -2
- package/dist/modules/customer_accounts/lib/customerAuth.js +7 -2
- package/dist/modules/customer_accounts/lib/customerAuth.js.map +2 -2
- package/dist/modules/customer_accounts/lib/customerAuthServer.js +2 -1
- package/dist/modules/customer_accounts/lib/customerAuthServer.js.map +2 -2
- package/dist/modules/customer_accounts/services/customerRbacService.js +12 -3
- package/dist/modules/customer_accounts/services/customerRbacService.js.map +2 -2
- package/dist/modules/customer_accounts/setup.js +6 -0
- package/dist/modules/customer_accounts/setup.js.map +2 -2
- package/dist/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.client.js +1 -1
- package/dist/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.client.js.map +2 -2
- package/dist/modules/customers/lib/visibilityFilter.js +6 -4
- package/dist/modules/customers/lib/visibilityFilter.js.map +2 -2
- package/dist/modules/dashboards/api/layout/[itemId]/route.js +6 -3
- package/dist/modules/dashboards/api/layout/[itemId]/route.js.map +2 -2
- package/dist/modules/dashboards/api/layout/route.js +22 -9
- package/dist/modules/dashboards/api/layout/route.js.map +2 -2
- package/dist/modules/dashboards/api/roles/widgets/route.js +9 -3
- package/dist/modules/dashboards/api/roles/widgets/route.js.map +2 -2
- package/dist/modules/dashboards/api/users/widgets/route.js +9 -3
- package/dist/modules/dashboards/api/users/widgets/route.js.map +2 -2
- package/dist/modules/dashboards/api/widgets/catalog.js +6 -3
- package/dist/modules/dashboards/api/widgets/catalog.js.map +2 -2
- package/dist/modules/dashboards/lib/access.js +5 -3
- package/dist/modules/dashboards/lib/access.js.map +2 -2
- package/dist/modules/directory/api/organization-branding/route.js +10 -3
- package/dist/modules/directory/api/organization-branding/route.js.map +2 -2
- package/dist/modules/directory/api/organizations/route.js +7 -0
- package/dist/modules/directory/api/organizations/route.js.map +3 -3
- package/dist/modules/directory/backend/directory/branding/page.js +31 -8
- package/dist/modules/directory/backend/directory/branding/page.js.map +2 -2
- package/dist/modules/directory/commands/organizations.js +8 -1
- package/dist/modules/directory/commands/organizations.js.map +2 -2
- package/dist/modules/directory/data/entities.js +4 -0
- package/dist/modules/directory/data/entities.js.map +2 -2
- package/dist/modules/directory/data/validators.js +2 -0
- package/dist/modules/directory/data/validators.js.map +2 -2
- package/dist/modules/directory/migrations/Migration20260626145500_directory_logo_preserve_aspect_ratio.js +13 -0
- package/dist/modules/directory/migrations/Migration20260626145500_directory_logo_preserve_aspect_ratio.js.map +7 -0
- package/dist/modules/entities/lib/entityAcl.js +47 -15
- package/dist/modules/entities/lib/entityAcl.js.map +3 -3
- package/dist/modules/inbox_ops/ai-tools.js +5 -2
- package/dist/modules/inbox_ops/ai-tools.js.map +2 -2
- package/dist/modules/messages/lib/routeHelpers.js +1 -3
- package/dist/modules/messages/lib/routeHelpers.js.map +2 -2
- package/dist/modules/notifications/lib/notificationRecipients.js +6 -7
- package/dist/modules/notifications/lib/notificationRecipients.js.map +2 -2
- package/dist/modules/query_index/di.js +3 -0
- package/dist/modules/query_index/di.js.map +2 -2
- package/dist/modules/query_index/lib/coverage.js +2 -2
- package/dist/modules/query_index/lib/coverage.js.map +2 -2
- package/dist/modules/query_index/lib/indexer.js +83 -17
- package/dist/modules/query_index/lib/indexer.js.map +2 -2
- package/dist/modules/query_index/lib/search-tokens.js +13 -7
- package/dist/modules/query_index/lib/search-tokens.js.map +2 -2
- package/dist/modules/query_index/subscribers/coverage_refresh.js +4 -0
- package/dist/modules/query_index/subscribers/coverage_refresh.js.map +2 -2
- package/dist/modules/query_index/subscribers/delete_one.js +53 -1
- package/dist/modules/query_index/subscribers/delete_one.js.map +3 -3
- package/dist/modules/query_index/subscribers/upsert_one.js +60 -0
- package/dist/modules/query_index/subscribers/upsert_one.js.map +3 -3
- package/dist/modules/sales/commands/documents.js +17 -0
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/staff/api/interceptors.js +5 -3
- package/dist/modules/staff/api/interceptors.js.map +2 -2
- package/dist/modules/sync_excel/api/upload/route.js +15 -2
- package/dist/modules/sync_excel/api/upload/route.js.map +2 -2
- package/dist/modules/workflows/lib/activity-executor.js +7 -7
- package/dist/modules/workflows/lib/activity-executor.js.map +2 -2
- package/generated/entities/organization/index.ts +1 -0
- package/generated/entity-fields-registry.ts +1 -0
- package/package.json +7 -7
- package/src/modules/auth/AGENTS.md +5 -3
- package/src/modules/auth/cli.ts +1 -1
- package/src/modules/auth/lib/backendChrome.tsx +5 -11
- package/src/modules/auth/lib/setup-app.ts +1 -0
- package/src/modules/auth/services/rbacService.ts +41 -46
- package/src/modules/catalog/widgets/injection/product-seo/widget.ts +1 -1
- package/src/modules/communication_channels/lib/access-control.ts +3 -3
- package/src/modules/customer_accounts/AGENTS.md +3 -3
- package/src/modules/customer_accounts/api/invitations/accept.ts +1 -2
- package/src/modules/customer_accounts/api/login.ts +4 -2
- package/src/modules/customer_accounts/api/magic-link/verify.ts +4 -2
- package/src/modules/customer_accounts/api/portal/feature-check.ts +5 -7
- package/src/modules/customer_accounts/api/portal/nav.ts +5 -3
- package/src/modules/customer_accounts/api/portal/profile.ts +5 -1
- package/src/modules/customer_accounts/api/portal/sessions-refresh.ts +3 -3
- package/src/modules/customer_accounts/lib/customerAuth.ts +8 -2
- package/src/modules/customer_accounts/lib/customerAuthServer.ts +1 -0
- package/src/modules/customer_accounts/services/customerRbacService.ts +16 -3
- package/src/modules/customer_accounts/setup.ts +7 -0
- package/src/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.client.tsx +1 -3
- package/src/modules/customers/lib/visibilityFilter.ts +7 -5
- package/src/modules/dashboards/api/layout/[itemId]/route.ts +6 -3
- package/src/modules/dashboards/api/layout/route.ts +22 -9
- package/src/modules/dashboards/api/roles/widgets/route.ts +9 -3
- package/src/modules/dashboards/api/users/widgets/route.ts +9 -3
- package/src/modules/dashboards/api/widgets/catalog.ts +6 -3
- package/src/modules/dashboards/lib/access.ts +5 -3
- package/src/modules/directory/api/organization-branding/route.ts +12 -1
- package/src/modules/directory/api/organizations/route.ts +7 -0
- package/src/modules/directory/backend/directory/branding/page.tsx +34 -9
- package/src/modules/directory/commands/organizations.ts +9 -1
- package/src/modules/directory/data/entities.ts +3 -0
- package/src/modules/directory/data/validators.ts +2 -0
- package/src/modules/directory/i18n/de.json +3 -1
- package/src/modules/directory/i18n/en.json +3 -1
- package/src/modules/directory/i18n/es.json +3 -1
- package/src/modules/directory/i18n/pl.json +3 -1
- package/src/modules/directory/migrations/.snapshot-open-mercato.json +18 -1
- package/src/modules/directory/migrations/Migration20260626145500_directory_logo_preserve_aspect_ratio.ts +13 -0
- package/src/modules/entities/lib/entityAcl.ts +47 -18
- package/src/modules/inbox_ops/ai-tools.ts +5 -3
- package/src/modules/messages/lib/routeHelpers.ts +4 -6
- package/src/modules/notifications/lib/notificationRecipients.ts +6 -8
- package/src/modules/query_index/di.ts +6 -0
- package/src/modules/query_index/lib/coverage.ts +9 -6
- package/src/modules/query_index/lib/indexer.ts +105 -19
- package/src/modules/query_index/lib/search-tokens.ts +19 -8
- package/src/modules/query_index/subscribers/coverage_refresh.ts +4 -0
- package/src/modules/query_index/subscribers/delete_one.ts +65 -1
- package/src/modules/query_index/subscribers/upsert_one.ts +68 -1
- package/src/modules/sales/commands/documents.ts +27 -0
- package/src/modules/sales/i18n/de.json +1 -0
- package/src/modules/sales/i18n/en.json +1 -0
- package/src/modules/sales/i18n/es.json +1 -0
- package/src/modules/sales/i18n/pl.json +1 -0
- package/src/modules/staff/api/interceptors.ts +6 -4
- package/src/modules/sync_excel/api/upload/route.ts +15 -2
- package/src/modules/workflows/lib/activity-executor.ts +12 -10
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
resolveWidgetAssignmentReadScope,
|
|
11
11
|
resolveWidgetAssignmentTargetAccess,
|
|
12
12
|
} from '@open-mercato/core/modules/dashboards/lib/widgetAssignmentScope'
|
|
13
|
-
import {
|
|
13
|
+
import { authorizeFeatures } from '@open-mercato/shared/security/featurePolicy'
|
|
14
14
|
import {
|
|
15
15
|
runCrudMutationGuardAfterSuccess,
|
|
16
16
|
validateCrudMutationGuard,
|
|
@@ -61,7 +61,10 @@ export async function GET(req: Request) {
|
|
|
61
61
|
const em = container.resolve('em') as any
|
|
62
62
|
const rbac = container.resolve('rbacService') as any
|
|
63
63
|
const acl = await rbac.loadAcl(auth.sub, { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null })
|
|
64
|
-
if (!
|
|
64
|
+
if (!authorizeFeatures([FEATURE], {
|
|
65
|
+
grantedFeatures: acl.features ?? [],
|
|
66
|
+
unrestricted: !!acl.isSuperAdmin,
|
|
67
|
+
})) {
|
|
65
68
|
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
|
|
66
69
|
}
|
|
67
70
|
|
|
@@ -120,7 +123,10 @@ export async function PUT(req: Request) {
|
|
|
120
123
|
const em = resolve('em') as any
|
|
121
124
|
const rbac = resolve('rbacService') as any
|
|
122
125
|
const acl = await rbac.loadAcl(auth.sub, { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null })
|
|
123
|
-
if (!
|
|
126
|
+
if (!authorizeFeatures([FEATURE], {
|
|
127
|
+
grantedFeatures: acl.features ?? [],
|
|
128
|
+
unrestricted: !!acl.isSuperAdmin,
|
|
129
|
+
})) {
|
|
124
130
|
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
|
|
125
131
|
}
|
|
126
132
|
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
resolveWidgetAssignmentReadScope,
|
|
12
12
|
resolveWidgetAssignmentTargetAccess,
|
|
13
13
|
} from '@open-mercato/core/modules/dashboards/lib/widgetAssignmentScope'
|
|
14
|
-
import {
|
|
14
|
+
import { authorizeFeatures } from '@open-mercato/shared/security/featurePolicy'
|
|
15
15
|
import {
|
|
16
16
|
runCrudMutationGuardAfterSuccess,
|
|
17
17
|
validateCrudMutationGuard,
|
|
@@ -44,7 +44,10 @@ export async function GET(req: Request) {
|
|
|
44
44
|
const em = container.resolve('em') as any
|
|
45
45
|
const rbac = container.resolve('rbacService') as any
|
|
46
46
|
const acl = await rbac.loadAcl(auth.sub, { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null })
|
|
47
|
-
if (!
|
|
47
|
+
if (!authorizeFeatures([FEATURE], {
|
|
48
|
+
grantedFeatures: acl.features ?? [],
|
|
49
|
+
unrestricted: !!acl.isSuperAdmin,
|
|
50
|
+
})) {
|
|
48
51
|
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
|
|
49
52
|
}
|
|
50
53
|
|
|
@@ -120,7 +123,10 @@ export async function PUT(req: Request) {
|
|
|
120
123
|
const em = resolve('em') as any
|
|
121
124
|
const rbac = resolve('rbacService') as any
|
|
122
125
|
const acl = await rbac.loadAcl(auth.sub, { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null })
|
|
123
|
-
if (!
|
|
126
|
+
if (!authorizeFeatures([FEATURE], {
|
|
127
|
+
grantedFeatures: acl.features ?? [],
|
|
128
|
+
unrestricted: !!acl.isSuperAdmin,
|
|
129
|
+
})) {
|
|
124
130
|
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
|
|
125
131
|
}
|
|
126
132
|
|
|
@@ -2,7 +2,6 @@ import { NextResponse } from 'next/server'
|
|
|
2
2
|
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
3
3
|
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
4
4
|
import { loadAllWidgets } from '@open-mercato/core/modules/dashboards/lib/widgets'
|
|
5
|
-
import { hasFeature } from '@open-mercato/shared/security/features'
|
|
6
5
|
import type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
7
6
|
import { dashboardsTag, dashboardsErrorSchema, dashboardWidgetCatalogSchema } from '../openapi'
|
|
8
7
|
|
|
@@ -16,8 +15,12 @@ export async function GET(req: Request) {
|
|
|
16
15
|
|
|
17
16
|
const { resolve } = await createRequestContainer()
|
|
18
17
|
const rbac = resolve('rbacService') as any
|
|
19
|
-
const
|
|
20
|
-
|
|
18
|
+
const canAssignWidgets = await rbac.userHasAllFeatures(
|
|
19
|
+
auth.sub,
|
|
20
|
+
['dashboards.admin.assign-widgets'],
|
|
21
|
+
{ tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null },
|
|
22
|
+
)
|
|
23
|
+
if (!canAssignWidgets) {
|
|
21
24
|
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
|
|
22
25
|
}
|
|
23
26
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
2
|
import { DashboardRoleWidgets, DashboardUserWidgets } from '../data/entities'
|
|
3
3
|
import { UserRole } from '@open-mercato/core/modules/auth/data/entities'
|
|
4
|
-
import {
|
|
4
|
+
import { authorizeFeatures } from '@open-mercato/shared/security/featurePolicy'
|
|
5
5
|
import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
6
6
|
|
|
7
7
|
type LoadedWidget = {
|
|
@@ -97,8 +97,10 @@ export async function resolveAllowedWidgetIds(
|
|
|
97
97
|
|
|
98
98
|
const filtered = widgets.filter((widget) => {
|
|
99
99
|
if (!baseSet.has(widget.metadata.id)) return false
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
return authorizeFeatures(widget.metadata.features ?? [], {
|
|
101
|
+
grantedFeatures: ctx.features,
|
|
102
|
+
unrestricted: ctx.isSuperAdmin,
|
|
103
|
+
})
|
|
102
104
|
})
|
|
103
105
|
|
|
104
106
|
return filtered.map((widget) => widget.metadata.id)
|
|
@@ -32,11 +32,13 @@ const brandingResponseSchema = z.object({
|
|
|
32
32
|
organizationName: z.string(),
|
|
33
33
|
tenantId: z.string().uuid(),
|
|
34
34
|
logoUrl: z.string().nullable(),
|
|
35
|
+
logoPreserveAspectRatio: z.boolean(),
|
|
35
36
|
updatedAt: z.string().nullable(),
|
|
36
37
|
})
|
|
37
38
|
|
|
38
39
|
const brandingUpdateSchema = z.object({
|
|
39
40
|
logoUrl: organizationUpdateSchema.shape.logoUrl,
|
|
41
|
+
logoPreserveAspectRatio: organizationUpdateSchema.shape.logoPreserveAspectRatio,
|
|
40
42
|
})
|
|
41
43
|
|
|
42
44
|
const errorSchema = z.object({
|
|
@@ -132,6 +134,7 @@ function toResponsePayload(organization: Organization, tenantId: string) {
|
|
|
132
134
|
organizationName: organization.name,
|
|
133
135
|
tenantId,
|
|
134
136
|
logoUrl: organization.logoUrl ?? null,
|
|
137
|
+
logoPreserveAspectRatio: !!organization.logoPreserveAspectRatio,
|
|
135
138
|
updatedAt: toIsoOrNull(organization.updatedAt),
|
|
136
139
|
}
|
|
137
140
|
}
|
|
@@ -211,7 +214,12 @@ export async function PUT(req: Request) {
|
|
|
211
214
|
operation: 'update',
|
|
212
215
|
requestMethod: req.method,
|
|
213
216
|
requestHeaders: req.headers,
|
|
214
|
-
mutationPayload: {
|
|
217
|
+
mutationPayload: {
|
|
218
|
+
logoUrl: parsed.data.logoUrl ?? null,
|
|
219
|
+
...(parsed.data.logoPreserveAspectRatio !== undefined
|
|
220
|
+
? { logoPreserveAspectRatio: parsed.data.logoPreserveAspectRatio }
|
|
221
|
+
: {}),
|
|
222
|
+
},
|
|
215
223
|
})
|
|
216
224
|
if (guardResult && !guardResult.ok) {
|
|
217
225
|
return NextResponse.json(guardResult.body, { status: guardResult.status })
|
|
@@ -232,6 +240,9 @@ export async function PUT(req: Request) {
|
|
|
232
240
|
id: resolved.organizationId,
|
|
233
241
|
tenantId: resolved.tenantId,
|
|
234
242
|
logoUrl: parsed.data.logoUrl ?? null,
|
|
243
|
+
...(parsed.data.logoPreserveAspectRatio !== undefined
|
|
244
|
+
? { logoPreserveAspectRatio: parsed.data.logoPreserveAspectRatio }
|
|
245
|
+
: {}),
|
|
235
246
|
},
|
|
236
247
|
ctx,
|
|
237
248
|
},
|
|
@@ -251,6 +251,7 @@ export async function GET(req: Request) {
|
|
|
251
251
|
id: stringId(org.id),
|
|
252
252
|
name: org.name,
|
|
253
253
|
logoUrl: org.logoUrl ?? null,
|
|
254
|
+
logoPreserveAspectRatio: !!org.logoPreserveAspectRatio,
|
|
254
255
|
parentId: org.parentId ?? null,
|
|
255
256
|
tenantId: tenantId,
|
|
256
257
|
isActive: !!org.isActive,
|
|
@@ -344,10 +345,12 @@ export async function GET(req: Request) {
|
|
|
344
345
|
const slugByOrgId = new Map<string, string | null>()
|
|
345
346
|
const updatedAtByOrgId = new Map<string, string | null>()
|
|
346
347
|
const logoUrlByOrgId = new Map<string, string | null>()
|
|
348
|
+
const logoPreserveAspectRatioByOrgId = new Map<string, boolean>()
|
|
347
349
|
for (const org of allOrgs) {
|
|
348
350
|
slugByOrgId.set(String(org.id), org.slug ?? null)
|
|
349
351
|
updatedAtByOrgId.set(String(org.id), org.updatedAt instanceof Date ? org.updatedAt.toISOString() : null)
|
|
350
352
|
logoUrlByOrgId.set(String(org.id), org.logoUrl ?? null)
|
|
353
|
+
logoPreserveAspectRatioByOrgId.set(String(org.id), !!org.logoPreserveAspectRatio)
|
|
351
354
|
}
|
|
352
355
|
|
|
353
356
|
const tenantIds = Array.from(byTenant.keys())
|
|
@@ -433,6 +436,7 @@ export async function GET(req: Request) {
|
|
|
433
436
|
slug: slugByOrgId.get(recordId) ?? null,
|
|
434
437
|
updatedAt: updatedAtByOrgId.get(recordId) ?? null,
|
|
435
438
|
logoUrl: logoUrlByOrgId.get(recordId) ?? null,
|
|
439
|
+
logoPreserveAspectRatio: logoPreserveAspectRatioByOrgId.get(recordId) ?? false,
|
|
436
440
|
tenantId: tid,
|
|
437
441
|
tenantName: tenantNameMap[tid] ?? tid,
|
|
438
442
|
parentId: node.parentId,
|
|
@@ -475,10 +479,12 @@ export async function GET(req: Request) {
|
|
|
475
479
|
const hierarchy = computeHierarchyForOrganizations(orgs, tenantId)
|
|
476
480
|
const slugByOrgId = new Map<string, string | null>()
|
|
477
481
|
const logoUrlByOrgId = new Map<string, string | null>()
|
|
482
|
+
const logoPreserveAspectRatioByOrgId = new Map<string, boolean>()
|
|
478
483
|
const updatedAtByOrgId = new Map<string, string | null>()
|
|
479
484
|
for (const org of orgs) {
|
|
480
485
|
slugByOrgId.set(String(org.id), org.slug ?? null)
|
|
481
486
|
logoUrlByOrgId.set(String(org.id), org.logoUrl ?? null)
|
|
487
|
+
logoPreserveAspectRatioByOrgId.set(String(org.id), !!org.logoPreserveAspectRatio)
|
|
482
488
|
updatedAtByOrgId.set(String(org.id), org.updatedAt instanceof Date ? org.updatedAt.toISOString() : null)
|
|
483
489
|
}
|
|
484
490
|
|
|
@@ -543,6 +549,7 @@ export async function GET(req: Request) {
|
|
|
543
549
|
name: node.name,
|
|
544
550
|
slug: slugByOrgId.get(recordId) ?? null,
|
|
545
551
|
logoUrl: logoUrlByOrgId.get(recordId) ?? null,
|
|
552
|
+
logoPreserveAspectRatio: logoPreserveAspectRatioByOrgId.get(recordId) ?? false,
|
|
546
553
|
updatedAt: updatedAtByOrgId.get(recordId) ?? null,
|
|
547
554
|
tenantId: node.tenantId,
|
|
548
555
|
tenantName,
|
|
@@ -12,6 +12,7 @@ import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimi
|
|
|
12
12
|
import { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'
|
|
13
13
|
import { Button } from '@open-mercato/ui/primitives/button'
|
|
14
14
|
import { Input } from '@open-mercato/ui/primitives/input'
|
|
15
|
+
import { SwitchField } from '@open-mercato/ui/primitives/switch-field'
|
|
15
16
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
16
17
|
|
|
17
18
|
type BrandingPayload = {
|
|
@@ -19,6 +20,7 @@ type BrandingPayload = {
|
|
|
19
20
|
organizationName: string
|
|
20
21
|
tenantId: string
|
|
21
22
|
logoUrl: string | null
|
|
23
|
+
logoPreserveAspectRatio: boolean
|
|
22
24
|
updatedAt: string | null
|
|
23
25
|
}
|
|
24
26
|
|
|
@@ -38,6 +40,7 @@ export default function OrganizationBrandingPage() {
|
|
|
38
40
|
const t = useT()
|
|
39
41
|
const queryClient = useQueryClient()
|
|
40
42
|
const [logoUrl, setLogoUrl] = React.useState('')
|
|
43
|
+
const [logoPreserveAspectRatio, setLogoPreserveAspectRatio] = React.useState(false)
|
|
41
44
|
const [selectedFile, setSelectedFile] = React.useState<File | null>(null)
|
|
42
45
|
const [filePreviewUrl, setFilePreviewUrl] = React.useState<string | null>(null)
|
|
43
46
|
const [saving, setSaving] = React.useState(false)
|
|
@@ -58,8 +61,9 @@ export default function OrganizationBrandingPage() {
|
|
|
58
61
|
|
|
59
62
|
React.useEffect(() => {
|
|
60
63
|
setLogoUrl(data?.logoUrl ?? '')
|
|
64
|
+
setLogoPreserveAspectRatio(data?.logoPreserveAspectRatio ?? false)
|
|
61
65
|
setSelectedFile(null)
|
|
62
|
-
}, [data?.logoUrl])
|
|
66
|
+
}, [data?.logoPreserveAspectRatio, data?.logoUrl])
|
|
63
67
|
|
|
64
68
|
React.useEffect(() => {
|
|
65
69
|
if (!selectedFile || typeof URL === 'undefined') {
|
|
@@ -89,12 +93,16 @@ export default function OrganizationBrandingPage() {
|
|
|
89
93
|
},
|
|
90
94
|
{ errorMessage: t('directory.branding.errors.upload', 'Failed to upload logo') },
|
|
91
95
|
)
|
|
92
|
-
return upload?.item.
|
|
96
|
+
return upload?.item.url ?? upload?.item.thumbnailUrl ?? null
|
|
93
97
|
}, [selectedFile, t])
|
|
94
98
|
|
|
95
|
-
const saveBranding = React.useCallback(async (
|
|
99
|
+
const saveBranding = React.useCallback(async (
|
|
100
|
+
nextLogoUrl?: string,
|
|
101
|
+
options?: { skipUpload?: boolean; logoPreserveAspectRatio?: boolean },
|
|
102
|
+
) => {
|
|
96
103
|
if (!data) return
|
|
97
104
|
const shouldUpload = Boolean(selectedFile && !options?.skipUpload)
|
|
105
|
+
const resolvedLogoPreserveAspectRatio = options?.logoPreserveAspectRatio ?? logoPreserveAspectRatio
|
|
98
106
|
setSaving(true)
|
|
99
107
|
try {
|
|
100
108
|
await runMutation({
|
|
@@ -108,7 +116,10 @@ export default function OrganizationBrandingPage() {
|
|
|
108
116
|
{
|
|
109
117
|
method: 'PUT',
|
|
110
118
|
headers: { 'content-type': 'application/json' },
|
|
111
|
-
body: JSON.stringify({
|
|
119
|
+
body: JSON.stringify({
|
|
120
|
+
logoUrl: resolvedLogoUrl || null,
|
|
121
|
+
logoPreserveAspectRatio: resolvedLogoPreserveAspectRatio,
|
|
122
|
+
}),
|
|
112
123
|
},
|
|
113
124
|
{ errorMessage: t('directory.branding.errors.save', 'Failed to update organization branding') },
|
|
114
125
|
),
|
|
@@ -123,6 +134,7 @@ export default function OrganizationBrandingPage() {
|
|
|
123
134
|
mutationPayload: {
|
|
124
135
|
organizationId: data.organizationId,
|
|
125
136
|
logoUrl: (nextLogoUrl ?? logoUrl.trim()) || null,
|
|
137
|
+
logoPreserveAspectRatio: resolvedLogoPreserveAspectRatio,
|
|
126
138
|
hasUpload: shouldUpload,
|
|
127
139
|
},
|
|
128
140
|
})
|
|
@@ -139,7 +151,7 @@ export default function OrganizationBrandingPage() {
|
|
|
139
151
|
} finally {
|
|
140
152
|
setSaving(false)
|
|
141
153
|
}
|
|
142
|
-
}, [data, logoUrl, queryClient, runMutation, selectedFile, t, uploadLogo])
|
|
154
|
+
}, [data, logoPreserveAspectRatio, logoUrl, queryClient, runMutation, selectedFile, t, uploadLogo])
|
|
143
155
|
|
|
144
156
|
const handleSubmit = React.useCallback((event: React.FormEvent<HTMLFormElement>) => {
|
|
145
157
|
event.preventDefault()
|
|
@@ -177,7 +189,7 @@ export default function OrganizationBrandingPage() {
|
|
|
177
189
|
<img
|
|
178
190
|
src={currentPreviewUrl}
|
|
179
191
|
alt={t('directory.branding.previewAlt', '{{name}} logo preview', { name: data.organizationName })}
|
|
180
|
-
className=
|
|
192
|
+
className={`h-full w-full ${logoPreserveAspectRatio ? 'object-contain' : 'rounded-full object-cover'}`}
|
|
181
193
|
/>
|
|
182
194
|
) : (
|
|
183
195
|
<ImagePlus className="size-10 text-muted-foreground" aria-hidden />
|
|
@@ -198,7 +210,7 @@ export default function OrganizationBrandingPage() {
|
|
|
198
210
|
ref={fileInputRef}
|
|
199
211
|
id="organization-logo-file"
|
|
200
212
|
type="file"
|
|
201
|
-
accept="image/png,image/jpeg,image/webp
|
|
213
|
+
accept="image/png,image/jpeg,image/webp"
|
|
202
214
|
onChange={(event) => {
|
|
203
215
|
const file = event.currentTarget.files?.[0]
|
|
204
216
|
if (!file) return
|
|
@@ -206,7 +218,7 @@ export default function OrganizationBrandingPage() {
|
|
|
206
218
|
}}
|
|
207
219
|
/>
|
|
208
220
|
<p className="text-xs text-muted-foreground">
|
|
209
|
-
{t('directory.branding.file.hint', 'PNG, JPG,
|
|
221
|
+
{t('directory.branding.file.hint', 'PNG, JPG, or WebP works best. Uploaded files are stored as organization attachments.')}
|
|
210
222
|
</p>
|
|
211
223
|
</div>
|
|
212
224
|
|
|
@@ -225,6 +237,18 @@ export default function OrganizationBrandingPage() {
|
|
|
225
237
|
</p>
|
|
226
238
|
</div>
|
|
227
239
|
|
|
240
|
+
<SwitchField
|
|
241
|
+
id="organization-logo-preserve-aspect-ratio"
|
|
242
|
+
label={t('directory.branding.aspectRatio.label', 'Keep the aspect ratio')}
|
|
243
|
+
description={t(
|
|
244
|
+
'directory.branding.aspectRatio.description',
|
|
245
|
+
'When disabled, the sidebar crops the logo into the standard icon shape.',
|
|
246
|
+
)}
|
|
247
|
+
checked={logoPreserveAspectRatio}
|
|
248
|
+
disabled={saving}
|
|
249
|
+
onCheckedChange={setLogoPreserveAspectRatio}
|
|
250
|
+
/>
|
|
251
|
+
|
|
228
252
|
<div className="flex flex-wrap items-center gap-2">
|
|
229
253
|
<Button type="submit" disabled={saving}>
|
|
230
254
|
{saving ? <Loader2 className="mr-2 size-4 animate-spin" aria-hidden /> : <Save className="mr-2 size-4" aria-hidden />}
|
|
@@ -238,7 +262,8 @@ export default function OrganizationBrandingPage() {
|
|
|
238
262
|
setSelectedFile(null)
|
|
239
263
|
if (fileInputRef.current) fileInputRef.current.value = ''
|
|
240
264
|
setLogoUrl('')
|
|
241
|
-
|
|
265
|
+
setLogoPreserveAspectRatio(false)
|
|
266
|
+
void saveBranding('', { skipUpload: true, logoPreserveAspectRatio: false })
|
|
242
267
|
}}
|
|
243
268
|
>
|
|
244
269
|
<RotateCcw className="mr-2 size-4" aria-hidden />
|
|
@@ -85,6 +85,7 @@ type OrganizationUndoSnapshot = {
|
|
|
85
85
|
name: string
|
|
86
86
|
slug?: string | null
|
|
87
87
|
logoUrl?: string | null
|
|
88
|
+
logoPreserveAspectRatio?: boolean
|
|
88
89
|
isActive: boolean
|
|
89
90
|
parentId: string | null
|
|
90
91
|
childParents: ChildParentSnapshot[]
|
|
@@ -122,6 +123,7 @@ function serializeOrganization(entity: Organization, custom?: Record<string, unk
|
|
|
122
123
|
name: entity.name,
|
|
123
124
|
slug: entity.slug ?? null,
|
|
124
125
|
logoUrl: entity.logoUrl ?? null,
|
|
126
|
+
logoPreserveAspectRatio: !!entity.logoPreserveAspectRatio,
|
|
125
127
|
isActive: !!entity.isActive,
|
|
126
128
|
parentId: entity.parentId ?? null,
|
|
127
129
|
ancestorIds: Array.isArray(entity.ancestorIds) ? [...entity.ancestorIds] : [],
|
|
@@ -147,6 +149,7 @@ function captureOrganizationSnapshots(
|
|
|
147
149
|
name: entity.name,
|
|
148
150
|
slug: entity.slug ?? null,
|
|
149
151
|
logoUrl: entity.logoUrl ?? null,
|
|
152
|
+
logoPreserveAspectRatio: !!entity.logoPreserveAspectRatio,
|
|
150
153
|
isActive: !!entity.isActive,
|
|
151
154
|
parentId: entity.parentId ?? null,
|
|
152
155
|
childParents: (childParents ?? []).map((entry) => ({
|
|
@@ -307,6 +310,7 @@ const createOrganizationCommand: CommandHandler<Record<string, unknown>, Organiz
|
|
|
307
310
|
name: parsed.name,
|
|
308
311
|
slug,
|
|
309
312
|
logoUrl: parsed.logoUrl ?? null,
|
|
313
|
+
logoPreserveAspectRatio: parsed.logoPreserveAspectRatio ?? false,
|
|
310
314
|
isActive: parsed.isActive ?? true,
|
|
311
315
|
parentId,
|
|
312
316
|
},
|
|
@@ -434,6 +438,7 @@ const createOrganizationCommand: CommandHandler<Record<string, unknown>, Organiz
|
|
|
434
438
|
existing.name = after.name
|
|
435
439
|
if (after.slug !== undefined) existing.slug = after.slug ?? null
|
|
436
440
|
if (after.logoUrl !== undefined) existing.logoUrl = after.logoUrl ?? null
|
|
441
|
+
if (after.logoPreserveAspectRatio !== undefined) existing.logoPreserveAspectRatio = !!after.logoPreserveAspectRatio
|
|
437
442
|
existing.isActive = after.isActive
|
|
438
443
|
existing.parentId = after.parentId
|
|
439
444
|
await em.flush()
|
|
@@ -446,6 +451,7 @@ const createOrganizationCommand: CommandHandler<Record<string, unknown>, Organiz
|
|
|
446
451
|
name: after.name,
|
|
447
452
|
slug: after.slug ?? null,
|
|
448
453
|
logoUrl: after.logoUrl ?? null,
|
|
454
|
+
logoPreserveAspectRatio: after.logoPreserveAspectRatio ?? false,
|
|
449
455
|
tenant: em.getReference(Tenant, tenantId),
|
|
450
456
|
isActive: after.isActive,
|
|
451
457
|
parentId: after.parentId,
|
|
@@ -571,6 +577,7 @@ const updateOrganizationCommand: CommandHandler<Record<string, unknown>, Organiz
|
|
|
571
577
|
if (parsed.name !== undefined) entity.name = parsed.name
|
|
572
578
|
if (resolvedSlug !== undefined) entity.slug = resolvedSlug
|
|
573
579
|
if (parsed.logoUrl !== undefined) entity.logoUrl = parsed.logoUrl ?? null
|
|
580
|
+
if (parsed.logoPreserveAspectRatio !== undefined) entity.logoPreserveAspectRatio = parsed.logoPreserveAspectRatio
|
|
574
581
|
if (parsed.isActive !== undefined) entity.isActive = parsed.isActive
|
|
575
582
|
entity.parentId = parentId
|
|
576
583
|
},
|
|
@@ -637,7 +644,7 @@ const updateOrganizationCommand: CommandHandler<Record<string, unknown>, Organiz
|
|
|
637
644
|
organizationId: String(result.id),
|
|
638
645
|
})
|
|
639
646
|
const after = serializeOrganization(result, custom)
|
|
640
|
-
const changes = buildChanges(beforeRecord, after as Record<string, unknown>, ['name', 'slug', 'logoUrl', 'isActive', 'parentId'])
|
|
647
|
+
const changes = buildChanges(beforeRecord, after as Record<string, unknown>, ['name', 'slug', 'logoUrl', 'logoPreserveAspectRatio', 'isActive', 'parentId'])
|
|
641
648
|
const customDiff = diffCustomFieldChanges(beforeRecord?.custom, custom)
|
|
642
649
|
for (const [key, diff] of Object.entries(customDiff)) {
|
|
643
650
|
changes[`cf_${key}`] = diff
|
|
@@ -675,6 +682,7 @@ const updateOrganizationCommand: CommandHandler<Record<string, unknown>, Organiz
|
|
|
675
682
|
entity.name = before.name
|
|
676
683
|
if (before.slug !== undefined) entity.slug = before.slug
|
|
677
684
|
if (before.logoUrl !== undefined) entity.logoUrl = before.logoUrl ?? null
|
|
685
|
+
if (before.logoPreserveAspectRatio !== undefined) entity.logoPreserveAspectRatio = !!before.logoPreserveAspectRatio
|
|
678
686
|
entity.isActive = before.isActive
|
|
679
687
|
entity.parentId = before.parentId
|
|
680
688
|
},
|
|
@@ -43,6 +43,9 @@ export class Organization {
|
|
|
43
43
|
@Property({ name: 'logo_url', type: 'text', nullable: true })
|
|
44
44
|
logoUrl?: string | null
|
|
45
45
|
|
|
46
|
+
@Property({ name: 'logo_preserve_aspect_ratio', type: 'boolean', default: false })
|
|
47
|
+
logoPreserveAspectRatio: boolean = false
|
|
48
|
+
|
|
46
49
|
@Property({ name: 'is_active', type: 'boolean', default: true })
|
|
47
50
|
isActive: boolean = true
|
|
48
51
|
|
|
@@ -28,6 +28,7 @@ export const organizationCreateSchema = z.object({
|
|
|
28
28
|
name: z.string().min(1).max(200),
|
|
29
29
|
slug: slugField,
|
|
30
30
|
logoUrl: logoUrlField,
|
|
31
|
+
logoPreserveAspectRatio: z.boolean().optional(),
|
|
31
32
|
isActive: z.boolean().optional(),
|
|
32
33
|
parentId: z.string().uuid().nullable().optional(),
|
|
33
34
|
childIds: z.array(z.string().uuid()).optional(),
|
|
@@ -39,6 +40,7 @@ export const organizationUpdateSchema = z.object({
|
|
|
39
40
|
name: z.string().min(1).max(200).optional(),
|
|
40
41
|
slug: slugField,
|
|
41
42
|
logoUrl: logoUrlField,
|
|
43
|
+
logoPreserveAspectRatio: z.boolean().optional(),
|
|
42
44
|
isActive: z.boolean().optional(),
|
|
43
45
|
parentId: z.string().uuid().nullable().optional(),
|
|
44
46
|
childIds: z.array(z.string().uuid()).optional(),
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
"directory.audit.tenants.update": "Mandanten aktualisieren",
|
|
8
8
|
"directory.branding.actions.reset": "Standardlogo verwenden",
|
|
9
9
|
"directory.branding.actions.save": "Branding speichern",
|
|
10
|
+
"directory.branding.aspectRatio.description": "Wenn deaktiviert, schneidet die Seitenleiste das Logo auf die Standard-Iconform zu.",
|
|
11
|
+
"directory.branding.aspectRatio.label": "Seitenverhältnis beibehalten",
|
|
10
12
|
"directory.branding.currentScope": "Aktuelle Organisation",
|
|
11
13
|
"directory.branding.description": "Legen Sie das Logo fest, das in der Backend-Seitenleiste für die aktuell ausgewählte Organisation verwendet wird.",
|
|
12
14
|
"directory.branding.errors.blocked": "Das Speichern des Brandings wurde blockiert.",
|
|
@@ -16,7 +18,7 @@
|
|
|
16
18
|
"directory.branding.errors.organizationRequired": "Wählen Sie eine einzelne Organisation aus, bevor Sie das Seitenleisten-Branding ändern.",
|
|
17
19
|
"directory.branding.errors.save": "Organisationsbranding konnte nicht aktualisiert werden",
|
|
18
20
|
"directory.branding.errors.upload": "Logo konnte nicht hochgeladen werden",
|
|
19
|
-
"directory.branding.file.hint": "PNG, JPG
|
|
21
|
+
"directory.branding.file.hint": "PNG, JPG oder WebP funktioniert am besten. Hochgeladene Dateien werden als Organisationsanhänge gespeichert.",
|
|
20
22
|
"directory.branding.file.label": "Logo hochladen",
|
|
21
23
|
"directory.branding.flash.saved": "Organisationsbranding aktualisiert",
|
|
22
24
|
"directory.branding.loading": "Organisationsbranding wird geladen...",
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
"directory.audit.tenants.update": "Update tenant",
|
|
8
8
|
"directory.branding.actions.reset": "Use default logo",
|
|
9
9
|
"directory.branding.actions.save": "Save branding",
|
|
10
|
+
"directory.branding.aspectRatio.description": "When disabled, the sidebar crops the logo into the standard icon shape.",
|
|
11
|
+
"directory.branding.aspectRatio.label": "Keep the aspect ratio",
|
|
10
12
|
"directory.branding.currentScope": "Current organization",
|
|
11
13
|
"directory.branding.description": "Set the logo used in the backend sidebar for the currently selected organization.",
|
|
12
14
|
"directory.branding.errors.blocked": "Branding save was blocked.",
|
|
@@ -16,7 +18,7 @@
|
|
|
16
18
|
"directory.branding.errors.organizationRequired": "Select a single organization before changing sidebar branding.",
|
|
17
19
|
"directory.branding.errors.save": "Failed to update organization branding",
|
|
18
20
|
"directory.branding.errors.upload": "Failed to upload logo",
|
|
19
|
-
"directory.branding.file.hint": "PNG, JPG,
|
|
21
|
+
"directory.branding.file.hint": "PNG, JPG, or WebP works best. Uploaded files are stored as organization attachments.",
|
|
20
22
|
"directory.branding.file.label": "Upload logo",
|
|
21
23
|
"directory.branding.flash.saved": "Organization branding updated",
|
|
22
24
|
"directory.branding.loading": "Loading organization branding...",
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
"directory.audit.tenants.update": "Actualizar inquilino",
|
|
8
8
|
"directory.branding.actions.reset": "Usar logo predeterminado",
|
|
9
9
|
"directory.branding.actions.save": "Guardar marca",
|
|
10
|
+
"directory.branding.aspectRatio.description": "Cuando está desactivado, la barra lateral recorta el logo con la forma de icono estándar.",
|
|
11
|
+
"directory.branding.aspectRatio.label": "Mantener la relación de aspecto",
|
|
10
12
|
"directory.branding.currentScope": "Organización actual",
|
|
11
13
|
"directory.branding.description": "Define el logo usado en la barra lateral del backend para la organización seleccionada.",
|
|
12
14
|
"directory.branding.errors.blocked": "Se bloqueó el guardado de la marca.",
|
|
@@ -16,7 +18,7 @@
|
|
|
16
18
|
"directory.branding.errors.organizationRequired": "Selecciona una sola organización antes de cambiar la marca de la barra lateral.",
|
|
17
19
|
"directory.branding.errors.save": "No se pudo actualizar la marca de la organización",
|
|
18
20
|
"directory.branding.errors.upload": "No se pudo subir el logo",
|
|
19
|
-
"directory.branding.file.hint": "PNG, JPG
|
|
21
|
+
"directory.branding.file.hint": "PNG, JPG o WebP funcionan mejor. Los archivos subidos se guardan como adjuntos de la organización.",
|
|
20
22
|
"directory.branding.file.label": "Subir logo",
|
|
21
23
|
"directory.branding.flash.saved": "Marca de la organización actualizada",
|
|
22
24
|
"directory.branding.loading": "Cargando marca de la organización...",
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
"directory.audit.tenants.update": "Aktualizuj najemcę",
|
|
8
8
|
"directory.branding.actions.reset": "Użyj domyślnego logo",
|
|
9
9
|
"directory.branding.actions.save": "Zapisz branding",
|
|
10
|
+
"directory.branding.aspectRatio.description": "Gdy opcja jest wyłączona, sidebar przycina logo do standardowego kształtu ikony.",
|
|
11
|
+
"directory.branding.aspectRatio.label": "Zachowaj proporcje",
|
|
10
12
|
"directory.branding.currentScope": "Aktualna organizacja",
|
|
11
13
|
"directory.branding.description": "Ustaw logo używane w sidebarze backendu dla aktualnie wybranej organizacji.",
|
|
12
14
|
"directory.branding.errors.blocked": "Zapis brandingu został zablokowany.",
|
|
@@ -16,7 +18,7 @@
|
|
|
16
18
|
"directory.branding.errors.organizationRequired": "Wybierz pojedynczą organizację przed zmianą brandingu sidebara.",
|
|
17
19
|
"directory.branding.errors.save": "Nie udało się zaktualizować brandingu organizacji",
|
|
18
20
|
"directory.branding.errors.upload": "Nie udało się przesłać logo",
|
|
19
|
-
"directory.branding.file.hint": "Najlepiej sprawdzi się PNG, JPG
|
|
21
|
+
"directory.branding.file.hint": "Najlepiej sprawdzi się PNG, JPG albo WebP. Przesłane pliki są zapisywane jako załączniki organizacji.",
|
|
20
22
|
"directory.branding.file.label": "Prześlij logo",
|
|
21
23
|
"directory.branding.flash.saved": "Branding organizacji zaktualizowany",
|
|
22
24
|
"directory.branding.loading": "Ładowanie brandingu organizacji...",
|
|
@@ -161,6 +161,23 @@
|
|
|
161
161
|
"enumItems": [],
|
|
162
162
|
"mappedType": "text"
|
|
163
163
|
},
|
|
164
|
+
"logo_preserve_aspect_ratio": {
|
|
165
|
+
"name": "logo_preserve_aspect_ratio",
|
|
166
|
+
"type": "boolean",
|
|
167
|
+
"unsigned": false,
|
|
168
|
+
"autoincrement": false,
|
|
169
|
+
"primary": false,
|
|
170
|
+
"nullable": false,
|
|
171
|
+
"unique": false,
|
|
172
|
+
"length": null,
|
|
173
|
+
"precision": null,
|
|
174
|
+
"scale": null,
|
|
175
|
+
"default": "false",
|
|
176
|
+
"comment": null,
|
|
177
|
+
"collation": null,
|
|
178
|
+
"enumItems": [],
|
|
179
|
+
"mappedType": "boolean"
|
|
180
|
+
},
|
|
164
181
|
"name": {
|
|
165
182
|
"name": "name",
|
|
166
183
|
"type": "text",
|
|
@@ -448,4 +465,4 @@
|
|
|
448
465
|
],
|
|
449
466
|
"views": [],
|
|
450
467
|
"nativeEnums": {}
|
|
451
|
-
}
|
|
468
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
|
|
3
|
+
export class Migration20260626145500DirectoryLogoPreserveAspectRatio extends Migration {
|
|
4
|
+
|
|
5
|
+
override up(): void | Promise<void> {
|
|
6
|
+
this.addSql(`alter table "organizations" add "logo_preserve_aspect_ratio" boolean not null default false;`);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
override down(): void | Promise<void> {
|
|
10
|
+
this.addSql(`alter table "organizations" drop column "logo_preserve_aspect_ratio";`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
}
|