@postxl/generators 1.18.0 → 1.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/backend-actions/actions.generator.d.ts +1 -0
- package/dist/backend-actions/actions.generator.js +6 -0
- package/dist/backend-actions/actions.generator.js.map +1 -1
- package/dist/backend-actions/generators/actions-module.generator.js +3 -2
- package/dist/backend-actions/generators/actions-module.generator.js.map +1 -1
- package/dist/backend-actions/generators/authorization-policy-service.generator.d.ts +2 -0
- package/dist/backend-actions/generators/authorization-policy-service.generator.js +214 -0
- package/dist/backend-actions/generators/authorization-policy-service.generator.js.map +1 -0
- package/dist/backend-actions/generators/authorization-service.generator.d.ts +1 -1
- package/dist/backend-actions/generators/authorization-service.generator.js +20 -8
- package/dist/backend-actions/generators/authorization-service.generator.js.map +1 -1
- package/dist/backend-actions/generators/dispatcher-service.generator.js +3 -2
- package/dist/backend-actions/generators/dispatcher-service.generator.js.map +1 -1
- package/dist/backend-ai/generators/ai-route.generator.js +3 -3
- package/dist/backend-authentication/authentication.generator.js +23 -1
- package/dist/backend-authentication/authentication.generator.js.map +1 -1
- package/dist/backend-authentication/generators/auth-guard.generator.js +5 -8
- package/dist/backend-authentication/generators/auth-guard.generator.js.map +1 -1
- package/dist/backend-authentication/generators/authentication-module.generator.js +1 -1
- package/dist/backend-authentication/generators/authentication-service.generator.js +11 -8
- package/dist/backend-authentication/generators/authentication-service.generator.js.map +1 -1
- package/dist/backend-authentication/generators/authentication-types.generator.js +4 -3
- package/dist/backend-authentication/generators/authentication-types.generator.js.map +1 -1
- package/dist/backend-authentication/template/src/authentication.config.ts +9 -0
- package/dist/backend-authentication/template/src/authentication.mock.service.ts +77 -13
- package/dist/backend-authentication/template/src/utils.ts +45 -0
- package/dist/backend-core/backend.generator.js +16 -0
- package/dist/backend-core/backend.generator.js.map +1 -1
- package/dist/backend-core/generators/api-config.generator.js +5 -0
- package/dist/backend-core/generators/api-config.generator.js.map +1 -1
- package/dist/backend-core/types.d.ts +4 -0
- package/dist/backend-excel-io/generators/excel-io-service.generator.js +27 -11
- package/dist/backend-excel-io/generators/excel-io-service.generator.js.map +1 -1
- package/dist/backend-excel-io/template/excel-io.controller.ts +3 -3
- package/dist/backend-rest-api/generators/model-controller.generator.js +9 -5
- package/dist/backend-rest-api/generators/model-controller.generator.js.map +1 -1
- package/dist/backend-rest-api/template/restApi/src/restApi.utils.ts +9 -0
- package/dist/backend-router-trpc/generators/audit-log-route.generator.js +2 -2
- package/dist/backend-router-trpc/generators/excel-io-route.generator.js +1 -1
- package/dist/backend-router-trpc/generators/middleware.generator.js +8 -5
- package/dist/backend-router-trpc/generators/middleware.generator.js.map +1 -1
- package/dist/backend-router-trpc/generators/model-routes.generator.js +27 -7
- package/dist/backend-router-trpc/generators/model-routes.generator.js.map +1 -1
- package/dist/backend-router-trpc/generators/trpc-plugin.generator.js +9 -6
- package/dist/backend-router-trpc/generators/trpc-plugin.generator.js.map +1 -1
- package/dist/backend-router-trpc/generators/trpc-shared.generator.js +4 -24
- package/dist/backend-router-trpc/generators/trpc-shared.generator.js.map +1 -1
- package/dist/backend-router-trpc/router-trpc.generator.d.ts +4 -0
- package/dist/backend-router-trpc/router-trpc.generator.js +1 -0
- package/dist/backend-router-trpc/router-trpc.generator.js.map +1 -1
- package/dist/backend-router-trpc/template/viewer.router.ts +1 -6
- package/dist/backend-update/update-actions.decoders.d.ts +4 -4
- package/dist/backend-upload/template/src/upload.controller.ts +1 -1
- package/dist/backend-upload/template/src/upload.service.ts +11 -5
- package/dist/backend-view/model-view-service.generator.js +105 -52
- package/dist/backend-view/model-view-service.generator.js.map +1 -1
- package/dist/backend-view/view.generator.d.ts +2 -1
- package/dist/backend-view/view.generator.js +8 -1
- package/dist/backend-view/view.generator.js.map +1 -1
- package/dist/base/base.generator.js +2 -0
- package/dist/base/base.generator.js.map +1 -1
- package/dist/e2e/template/e2e/specs/example.spec.ts-snapshots/Navigate-to-homepage-and-take-snapshot-1-chromium-linux.png +0 -0
- package/dist/frontend-admin/admin.generator.js +2 -0
- package/dist/frontend-admin/admin.generator.js.map +1 -1
- package/dist/frontend-admin/generators/authorization-utils.generator.d.ts +1 -0
- package/dist/frontend-admin/generators/authorization-utils.generator.js +20 -0
- package/dist/frontend-admin/generators/authorization-utils.generator.js.map +1 -0
- package/dist/frontend-admin/generators/comment-sidebar.generator.js +9 -1
- package/dist/frontend-admin/generators/comment-sidebar.generator.js.map +1 -1
- package/dist/frontend-admin/generators/model-admin-page.generator.js +347 -184
- package/dist/frontend-admin/generators/model-admin-page.generator.js.map +1 -1
- package/dist/frontend-core/frontend.generator.d.ts +6 -0
- package/dist/frontend-core/frontend.generator.js +10 -3
- package/dist/frontend-core/frontend.generator.js.map +1 -1
- package/dist/frontend-core/template/README.md +2 -0
- package/dist/frontend-core/template/src/context-providers/auth-context-provider.tsx +1 -2
- package/dist/frontend-core/template/src/pages/dashboard/dashboard.page.tsx +10 -1
- package/dist/frontend-core/template/src/pages/login/login.page.tsx +1 -1
- package/dist/frontend-core/template/vite.config.ts +5 -0
- package/dist/frontend-core/types/component.d.ts +1 -1
- package/dist/frontend-core/types/contextprovider.d.ts +1 -1
- package/dist/frontend-core/types/hook.d.ts +1 -1
- package/dist/frontend-trpc-client/generators/model-hook.generator.js +104 -39
- package/dist/frontend-trpc-client/generators/model-hook.generator.js.map +1 -1
- package/dist/frontend-trpc-client/trpc-client.generator.js +28 -14
- package/dist/frontend-trpc-client/trpc-client.generator.js.map +1 -1
- package/dist/types/types.generator.d.ts +7 -0
- package/dist/types/types.generator.js +80 -0
- package/dist/types/types.generator.js.map +1 -1
- package/package.json +3 -3
|
@@ -184,6 +184,14 @@ export function ${context.admin.components.commentSidebar.name}({ model, filtere
|
|
|
184
184
|
}, [currentUserId, createComment, model, commentTarget])
|
|
185
185
|
|
|
186
186
|
const selectedLabel = selectedEntityId ? labelMap?.[selectedEntityId] ?? selectedEntityId : undefined
|
|
187
|
+
let commentListOnCreate: ((text: string) => void) | undefined
|
|
188
|
+
if (currentUserId) {
|
|
189
|
+
if (singleCommentThread && groups.length === 1) {
|
|
190
|
+
commentListOnCreate = (text: string) => handleCreate(text, groups[0].groupId)
|
|
191
|
+
} else {
|
|
192
|
+
commentListOnCreate = handleCreate
|
|
193
|
+
}
|
|
194
|
+
}
|
|
187
195
|
|
|
188
196
|
if (!commentsLoaded) {
|
|
189
197
|
return (
|
|
@@ -237,7 +245,7 @@ export function ${context.admin.components.commentSidebar.name}({ model, filtere
|
|
|
237
245
|
comments={singleCommentThread && groups.length === 1 ? groups[0].comments : groups}
|
|
238
246
|
onResolve={handleResolve}
|
|
239
247
|
onReply={currentUserId ? handleReply : undefined}
|
|
240
|
-
onCreate={
|
|
248
|
+
onCreate={commentListOnCreate}
|
|
241
249
|
/>
|
|
242
250
|
)}
|
|
243
251
|
</div>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"comment-sidebar.generator.js","sourceRoot":"","sources":["../../../src/frontend-admin/generators/comment-sidebar.generator.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AASA,
|
|
1
|
+
{"version":3,"file":"comment-sidebar.generator.js","sourceRoot":"","sources":["../../../src/frontend-admin/generators/comment-sidebar.generator.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AASA,wDAuNC;AAhOD,6DAA8C;AAI9C;;;;GAIG;AACH,SAAgB,sBAAsB,CAAC,EAAE,OAAO,EAA8B;IAC5E,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,SAAgB,CAAE,CAAA;IAC1D,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,MAAa,CAAE,CAAA;IAEpD,MAAM,OAAO,GAAG,SAAS,CAAC,eAAe;QACvC,EAAE;SACD,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,cAAe,CAAC,QAAQ,CAAC;SACvD,SAAS,CAAC;QACT,IAAI,EAAE,SAAS,CAAC,aAAa,CAAC,OAAO,CAAC;QACtC,KAAK,EAAE;YACL,SAAS,CAAC,cAAc,CAAC,UAAU,CAAC;YACpC,SAAS,CAAC,cAAc,CAAC,SAAS,CAAC;YACnC,SAAS,CAAC,cAAc,CAAC,aAAa,CAAC;YACvC,SAAS,CAAC,cAAc,CAAC,WAAW,CAAC;SACtC;KACF,CAAC;SACD,GAAG,CAAC,YAAY,CAAC,SAAS,CAAC;SAC3B,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC;SACxB,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;SAC9B,OAAO,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;IAE9B,MAAM,cAAc,GAAG,SAAS,CAAC,UAAU,CAAC,IAAI,CAAA;IAEhD,OAAO;EACP,OAAO,CAAC,QAAQ,EAAE;;;;;;;;;;;;;;kBAcF,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,cAAe,CAAC,IAAI;;;;;;MAMzD,YAAY,CAAC,SAAS,CAAC,YAAY,CAAC,IAAI;MACxC,YAAY,CAAC,SAAS,CAAC,MAAO,CAAC,IAAI;MACnC,YAAY,CAAC,SAAS,CAAC,WAAY,CAAC,IAAI;MACxC,YAAY,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI;QAClC,YAAY,CAAC,SAAS,CAAC,IAAI;;;;;;;;;YASvB,SAAS,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,iBAAiB,SAAS,CAAC,SAAS,CAAC,IAAI;;;;;;;;;;0BAUvD,cAAc;;;;yDAIiB,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iDAiCtB,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI;;;;;;;;iCAQ1C,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI;;;;sCAIlB,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI;;;;;;;;iCAQ/B,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI;;;;;;;;;;;;iCAYvB,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8EvD,CAAA;AACD,CAAC"}
|
|
@@ -39,6 +39,8 @@ const Generator = __importStar(require("@postxl/generator"));
|
|
|
39
39
|
* Generator function that generates the Admin page for a model
|
|
40
40
|
*/
|
|
41
41
|
function generateModelAdminPage({ model, context, }) {
|
|
42
|
+
const adminUiPolicy = resolveAdminUiPolicy(model, context);
|
|
43
|
+
const writePolicy = resolveWritePolicy(model, context);
|
|
42
44
|
const imports = Generator.ImportGenerator
|
|
43
45
|
//
|
|
44
46
|
.from(model.admin.page.pageComponent.location)
|
|
@@ -100,115 +102,45 @@ function generateModelAdminPage({ model, context, }) {
|
|
|
100
102
|
],
|
|
101
103
|
});
|
|
102
104
|
const showComments = model.name !== 'Comment' && !!context.admin.components.commentSidebar;
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
Generator.toFunctionName('ListTree'),
|
|
120
|
-
Generator.toFunctionName('ScrollText'),
|
|
121
|
-
Generator.toFunctionName('Sparkles'),
|
|
122
|
-
],
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
const relatedModels = [];
|
|
126
|
-
for (const relatedModelName of model.relatedModelNames) {
|
|
127
|
-
const relatedModel = context.models.get(relatedModelName);
|
|
128
|
-
imports.add(relatedModel.itemsHook);
|
|
129
|
-
relatedModels.push({
|
|
130
|
-
model: relatedModel,
|
|
131
|
-
mapName: relatedModel.internalPluralName,
|
|
132
|
-
mapDefinition: `const {${relatedModel.itemsHook.map.name}: ${relatedModel.internalPluralName}} = ${relatedModel.itemsHook.name}()`,
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
let createModalHook = '';
|
|
136
|
-
let createModalIntentHook = '';
|
|
137
|
-
let createModal = '';
|
|
138
|
-
if (!model.isReadonly) {
|
|
139
|
-
imports
|
|
140
|
-
//
|
|
141
|
-
.addType(model.types.id)
|
|
142
|
-
.addImport({ from: Generator.toPackageName('react'), items: [Generator.toFunctionName('useRef')] })
|
|
143
|
-
.addImport({ from: Generator.toPackageName('sonner'), items: [Generator.toFunctionName('toast')] })
|
|
144
|
-
.addImport({ from: model.forms.createModal.location, items: [model.forms.createModal.name] });
|
|
145
|
-
// In case the model does not provide any explicit label field, we generally use the id field as fallback.
|
|
146
|
-
// However, in case of creating a new item, we do not have an Id yet and hence cannot show it in success and error messages.
|
|
147
|
-
const hasLabelField = model.labelField.name !== model.idField.name;
|
|
148
|
-
const variableName = hasLabelField ? `created${model.name}` : '_';
|
|
149
|
-
const label = hasLabelField ? ` "\${${variableName}.${model.labelField.name}}"` : '';
|
|
150
|
-
createModalHook = ` const [isCreateModalOpen, setIsCreateModalOpen] = useState(false)`;
|
|
151
|
-
createModalIntentHook = `
|
|
152
|
-
useEffect(() => {
|
|
153
|
-
if (typeof globalThis === 'undefined') {
|
|
154
|
-
return
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const storageKey = 'pxl:create-intent-model'
|
|
158
|
-
const targetModel = '${model.name}'
|
|
159
|
-
const openFromIntent = (intentModel: unknown) => {
|
|
160
|
-
if (intentModel === targetModel) {
|
|
161
|
-
setIsCreateModalOpen(true)
|
|
162
|
-
globalThis.sessionStorage.removeItem(storageKey)
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
openFromIntent(globalThis.sessionStorage.getItem(storageKey))
|
|
167
|
-
|
|
168
|
-
const onIntent = (event: Event) => {
|
|
169
|
-
const detail = (event as CustomEvent<{ model?: string }>).detail
|
|
170
|
-
openFromIntent(detail?.model)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
globalThis.addEventListener('pxl:create-intent', onIntent)
|
|
174
|
-
return () => {
|
|
175
|
-
globalThis.removeEventListener('pxl:create-intent', onIntent)
|
|
176
|
-
}
|
|
177
|
-
}, [])`;
|
|
178
|
-
createModal = `
|
|
179
|
-
<${model.forms.createModal.name}
|
|
180
|
-
open={isCreateModalOpen}
|
|
181
|
-
onClose={() => setIsCreateModalOpen(false)}
|
|
182
|
-
onSuccess={(${variableName}) => {
|
|
183
|
-
toast.success(\`${model.userFriendlyName}${label} created successfully!\`)
|
|
184
|
-
setIsCreateModalOpen(false)
|
|
185
|
-
}}
|
|
186
|
-
onError={(${variableName}, error) => {
|
|
187
|
-
toast.error(\`Error creating ${model.userFriendlyName}${label}: \${String(error)}\`)
|
|
188
|
-
}}
|
|
189
|
-
/>`;
|
|
190
|
-
}
|
|
105
|
+
configureSidebarImports({ imports, context, showComments });
|
|
106
|
+
const relatedModels = collectRelatedModels({ model, context, imports });
|
|
107
|
+
const createModalContent = resolveCreateModalContent(model, imports);
|
|
108
|
+
const canAccessAdminExpression = toRoleExpression(adminUiPolicy, 'userRoles');
|
|
109
|
+
const canWriteModelExpression = toRoleExpression(writePolicy, 'userRoles');
|
|
110
|
+
const commentCountState = buildCommentCountState(showComments);
|
|
111
|
+
const modelHookMutationFields = buildModelHookMutationFields(model);
|
|
112
|
+
const commentSidebarLogic = buildCommentSidebarLogic({ showComments, model });
|
|
113
|
+
const commentSelectedEntityId = buildCommentSelectedEntityId(showComments);
|
|
114
|
+
const cellUpdateHandler = buildCellUpdateHandler(model);
|
|
115
|
+
const removeRowsHandler = buildRemoveRowsHandler(model);
|
|
116
|
+
const commentSidebarTab = buildCommentSidebarTab({ showComments, model });
|
|
117
|
+
const relatedTableProps = buildRelatedTableProps(relatedModels);
|
|
118
|
+
const addRowProp = buildAddRowProp(model);
|
|
119
|
+
const cellUpdateProp = buildCellUpdateProp(model);
|
|
120
|
+
const removeRowsProp = buildRemoveRowsProp(model);
|
|
191
121
|
return `
|
|
192
|
-
import {
|
|
122
|
+
import { SidebarTab, useDebouncedCallback } from '@postxl/ui-components'
|
|
193
123
|
import { APP_CONFIG } from '@lib/config'
|
|
194
124
|
import { useAuth } from '@context-providers/auth-context-provider'
|
|
125
|
+
import { hasAnyRole } from '@lib/authorization'
|
|
195
126
|
${imports.generate()}
|
|
196
127
|
|
|
197
128
|
export default function ${model.admin.page.pageComponent.name}() {
|
|
198
129
|
const navigate = useNavigate()
|
|
199
130
|
const { viewerData } = useAuth()
|
|
200
131
|
const currentUserId = viewerData?.user?.id
|
|
201
|
-
const
|
|
202
|
-
|
|
132
|
+
const userRoles = viewerData?.userRoles ?? []
|
|
133
|
+
const canAccessAdminPage = ${canAccessAdminExpression}
|
|
134
|
+
const canWriteModel = ${canWriteModelExpression}
|
|
135
|
+
${commentCountState}
|
|
203
136
|
|
|
204
137
|
const { selectedKey, focusedCell } = useSearch({
|
|
205
138
|
from: '/_auth-routes${model.admin.page.route}',
|
|
206
139
|
select: (search) => ({ selectedKey: search.selectedKey, focusedCell: search.focusedCell }),
|
|
207
140
|
})
|
|
208
|
-
|
|
209
|
-
${
|
|
210
|
-
${
|
|
211
|
-
|
|
141
|
+
|
|
142
|
+
${createModalContent.hook}
|
|
143
|
+
${createModalContent.intentHook}
|
|
212
144
|
// Track selected row IDs from checkbox selection
|
|
213
145
|
const [selectedRowIds, setSelectedRowIds] = useState<string[]>([])
|
|
214
146
|
|
|
@@ -226,7 +158,7 @@ export default function ${model.admin.page.pageComponent.name}() {
|
|
|
226
158
|
hasNextPage,
|
|
227
159
|
fetchNextPage,
|
|
228
160
|
total,
|
|
229
|
-
${
|
|
161
|
+
${modelHookMutationFields}
|
|
230
162
|
} = ${model.itemsHook.name}({ filters, sort: sortState })
|
|
231
163
|
|
|
232
164
|
// Queries for related models' maps used by the table (passed as separate props)
|
|
@@ -244,18 +176,7 @@ export default function ${model.admin.page.pageComponent.name}() {
|
|
|
244
176
|
return focusedCell
|
|
245
177
|
}, [focusedCell])
|
|
246
178
|
|
|
247
|
-
${
|
|
248
|
-
? `// Comment sidebar: compute filtered entity IDs and label map from table data
|
|
249
|
-
const commentFilteredIds = useMemo(() =>
|
|
250
|
-
filteredDataList?.map(item => String(item.id)) ?? [],
|
|
251
|
-
[filteredDataList]
|
|
252
|
-
)
|
|
253
|
-
const commentLabelMap = useMemo(() =>
|
|
254
|
-
Object.fromEntries((filteredDataList ?? []).map(item => [String(item.id), String(item.${model.labelField.name})])),
|
|
255
|
-
[filteredDataList]
|
|
256
|
-
)
|
|
257
|
-
`
|
|
258
|
-
: ''}
|
|
179
|
+
${commentSidebarLogic}
|
|
259
180
|
// Callback when row selection changes (checkbox)
|
|
260
181
|
const handleRowSelectionChange = useCallback((rowIds: string[]) => {
|
|
261
182
|
setSelectedRowIds(rowIds)
|
|
@@ -298,7 +219,7 @@ export default function ${model.admin.page.pageComponent.name}() {
|
|
|
298
219
|
// Callback when cell focus changes in the table — also sets selectedKey from the focused row
|
|
299
220
|
const handleCellChange = useCallback((args: { rowIndex: number; columnId: string }) => {
|
|
300
221
|
const row = filteredDataList?.[args.rowIndex]
|
|
301
|
-
const key = row?.${model.keyField.name}
|
|
222
|
+
const key = row?.${model.keyField.name} == null ? undefined : String(row.${model.keyField.name})
|
|
302
223
|
void navigate({ to: '${model.admin.page.route}', search: { selectedKey: key, focusedCell: args.columnId || undefined } })
|
|
303
224
|
// Clear checkbox selection when focusing a cell
|
|
304
225
|
setSelectedRowIds([])
|
|
@@ -306,6 +227,18 @@ export default function ${model.admin.page.pageComponent.name}() {
|
|
|
306
227
|
|
|
307
228
|
useEffect(() => {
|
|
308
229
|
const prefix = 'admin-${model._conjugated.kebabCase}-filter'
|
|
230
|
+
const getFilterInputSchema = (valueType: string) => {
|
|
231
|
+
if (valueType === 'number') {
|
|
232
|
+
return numberFilterInputSchema
|
|
233
|
+
}
|
|
234
|
+
if (valueType === 'date') {
|
|
235
|
+
return dateFilterInputSchema
|
|
236
|
+
}
|
|
237
|
+
if (valueType === 'boolean') {
|
|
238
|
+
return booleanFilterInputSchema
|
|
239
|
+
}
|
|
240
|
+
return stringFilterInputSchema
|
|
241
|
+
}
|
|
309
242
|
|
|
310
243
|
const filterActions = Object.entries(${model.types.filter.config.name}).map(([field, fieldConfig]) =>
|
|
311
244
|
input({
|
|
@@ -313,14 +246,7 @@ export default function ${model.admin.page.pageComponent.name}() {
|
|
|
313
246
|
visibility: 'visible',
|
|
314
247
|
group: 'Filter records by...',
|
|
315
248
|
label: toFilterActionLabel(field),
|
|
316
|
-
inputSchema:
|
|
317
|
-
fieldConfig.valueType === 'number'
|
|
318
|
-
? numberFilterInputSchema
|
|
319
|
-
: fieldConfig.valueType === 'date'
|
|
320
|
-
? dateFilterInputSchema
|
|
321
|
-
: fieldConfig.valueType === 'boolean'
|
|
322
|
-
? booleanFilterInputSchema
|
|
323
|
-
: stringFilterInputSchema,
|
|
249
|
+
inputSchema: getFilterInputSchema(fieldConfig.valueType),
|
|
324
250
|
run: async (query, inputValue) => {
|
|
325
251
|
if (fieldConfig.valueType === 'number') {
|
|
326
252
|
const parsed = numberFilterInputSchema.safeParse(inputValue)
|
|
@@ -414,48 +340,15 @@ export default function ${model.admin.page.pageComponent.name}() {
|
|
|
414
340
|
setSearchInput,
|
|
415
341
|
])
|
|
416
342
|
|
|
417
|
-
${
|
|
418
|
-
? ''
|
|
419
|
-
: `// Batch cell updates: collect updates within the same microtask and send as one updateFieldMany
|
|
420
|
-
const pendingUpdatesRef = useRef<Map<string, { field: string; value: unknown; ids: ${model.types.id.name}[] }> | null>(null)
|
|
421
|
-
|
|
422
|
-
const handleCellUpdate = useCallback((args: { rowIndex: number; columnId: string; value: unknown }) => {
|
|
423
|
-
const row = filteredDataList?.[args.rowIndex]
|
|
424
|
-
if (!row) return
|
|
425
|
-
|
|
426
|
-
// Initialize batch map and schedule flush on first call in this microtask
|
|
427
|
-
if (!pendingUpdatesRef.current) {
|
|
428
|
-
pendingUpdatesRef.current = new Map()
|
|
429
|
-
queueMicrotask(() => {
|
|
430
|
-
const pending = pendingUpdatesRef.current
|
|
431
|
-
pendingUpdatesRef.current = null
|
|
432
|
-
if (!pending) return
|
|
343
|
+
${commentSelectedEntityId}
|
|
433
344
|
|
|
434
|
-
|
|
435
|
-
if (entry.ids.length === 1) {
|
|
436
|
-
void updateFieldMutation({ id: entry.ids[0], field: entry.field, value: entry.value } as Parameters<typeof updateFieldMutation>[0])
|
|
437
|
-
} else {
|
|
438
|
-
void updateFieldManyMutation({ ids: entry.ids, field: entry.field, value: entry.value } as Parameters<typeof updateFieldManyMutation>[0])
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
})
|
|
442
|
-
}
|
|
345
|
+
${cellUpdateHandler}
|
|
443
346
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
existing.ids.push(row.id)
|
|
449
|
-
} else {
|
|
450
|
-
pendingUpdatesRef.current.set(key, { field: args.columnId, value: args.value, ids: [row.id] })
|
|
451
|
-
}
|
|
452
|
-
}, [filteredDataList, updateFieldMutation, updateFieldManyMutation])
|
|
453
|
-
|
|
454
|
-
function handleRemoveRows(rows: ${model.types.id.name}[]) {
|
|
455
|
-
void deleteManyMutation(rows)
|
|
347
|
+
${removeRowsHandler}
|
|
348
|
+
|
|
349
|
+
if (!canAccessAdminPage) {
|
|
350
|
+
return null
|
|
456
351
|
}
|
|
457
|
-
`}
|
|
458
|
-
|
|
459
352
|
|
|
460
353
|
return (
|
|
461
354
|
<>
|
|
@@ -467,24 +360,7 @@ export default function ${model.admin.page.pageComponent.name}() {
|
|
|
467
360
|
>
|
|
468
361
|
<AdminSidebarNavContent />
|
|
469
362
|
</SidebarTab>
|
|
470
|
-
${
|
|
471
|
-
? `<SidebarTab
|
|
472
|
-
side="right"
|
|
473
|
-
id="comment-content"
|
|
474
|
-
icon={MessageSquareText}
|
|
475
|
-
label="Comment"
|
|
476
|
-
order={0}
|
|
477
|
-
badge={{ label: String(commentCount) }}
|
|
478
|
-
>
|
|
479
|
-
<ModelCommentContent
|
|
480
|
-
model="${model.name}"
|
|
481
|
-
filteredIds={commentFilteredIds}
|
|
482
|
-
labelMap={commentLabelMap}
|
|
483
|
-
selectedEntityId={selectedEntityId ? String(selectedEntityId) : selectedRowIds.length === 1 ? selectedRowIds[0] : undefined}
|
|
484
|
-
onCommentCountChange={setCommentCount}
|
|
485
|
-
/>
|
|
486
|
-
</SidebarTab>`
|
|
487
|
-
: ''}
|
|
363
|
+
${commentSidebarTab}
|
|
488
364
|
<SidebarTab
|
|
489
365
|
side="right"
|
|
490
366
|
id="audit-log-content"
|
|
@@ -514,7 +390,7 @@ export default function ${model.admin.page.pageComponent.name}() {
|
|
|
514
390
|
<AiSidebarContent />
|
|
515
391
|
</SidebarTab>
|
|
516
392
|
)}
|
|
517
|
-
<
|
|
393
|
+
<div className="p-4 overflow-hidden gap-4">
|
|
518
394
|
<h3 className="font-bold text-[1.75rem]">${model.userFriendlyName}</h3>
|
|
519
395
|
<${model.table.component.name}
|
|
520
396
|
data={filteredDataList ?? []}
|
|
@@ -534,11 +410,9 @@ export default function ${model.admin.page.pageComponent.name}() {
|
|
|
534
410
|
onSearchInputChange={handleSearchInputChange}
|
|
535
411
|
sort={sortState}
|
|
536
412
|
onSortChange={setSortState}
|
|
537
|
-
${
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
handleAddRow={() => setIsCreateModalOpen(true)}
|
|
541
|
-
handleRemoveRows={handleRemoveRows}`}
|
|
413
|
+
${cellUpdateProp}
|
|
414
|
+
${addRowProp}
|
|
415
|
+
${removeRowsProp}
|
|
542
416
|
onCellChange={handleCellChange}
|
|
543
417
|
onRowSelectionChange={handleRowSelectionChange}
|
|
544
418
|
selectedKey={selectedKey}
|
|
@@ -548,17 +422,306 @@ export default function ${model.admin.page.pageComponent.name}() {
|
|
|
548
422
|
total={total}
|
|
549
423
|
model="${model._conjugated.camelCase}"
|
|
550
424
|
currentUserId={currentUserId}
|
|
551
|
-
isAdmin={
|
|
425
|
+
isAdmin={canWriteModel}
|
|
552
426
|
onFiltersReplace={setFilters}
|
|
553
427
|
onSortReplace={setSortState}
|
|
554
|
-
${
|
|
428
|
+
${relatedTableProps}
|
|
555
429
|
/>
|
|
556
|
-
</
|
|
430
|
+
</div>
|
|
557
431
|
|
|
558
|
-
${
|
|
432
|
+
${createModalContent.modal}
|
|
559
433
|
</>
|
|
560
434
|
)
|
|
561
435
|
}
|
|
562
436
|
`;
|
|
563
437
|
}
|
|
438
|
+
function configureSidebarImports({ imports, context, showComments, }) {
|
|
439
|
+
if (showComments) {
|
|
440
|
+
imports.add(context.admin.components.commentSidebar);
|
|
441
|
+
imports.addImport({
|
|
442
|
+
from: Generator.toPackageName('lucide-react'),
|
|
443
|
+
items: [
|
|
444
|
+
Generator.toFunctionName('ListTree'),
|
|
445
|
+
Generator.toFunctionName('MessageSquareText'),
|
|
446
|
+
Generator.toFunctionName('ScrollText'),
|
|
447
|
+
Generator.toFunctionName('Sparkles'),
|
|
448
|
+
],
|
|
449
|
+
});
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
imports.addImport({
|
|
453
|
+
from: Generator.toPackageName('lucide-react'),
|
|
454
|
+
items: [
|
|
455
|
+
Generator.toFunctionName('ListTree'),
|
|
456
|
+
Generator.toFunctionName('ScrollText'),
|
|
457
|
+
Generator.toFunctionName('Sparkles'),
|
|
458
|
+
],
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
function collectRelatedModels({ model, context, imports, }) {
|
|
462
|
+
const relatedModels = [];
|
|
463
|
+
for (const relatedModelName of model.relatedModelNames) {
|
|
464
|
+
const relatedModel = context.models.get(relatedModelName);
|
|
465
|
+
imports.add(relatedModel.itemsHook);
|
|
466
|
+
relatedModels.push({
|
|
467
|
+
model: relatedModel,
|
|
468
|
+
mapName: relatedModel.internalPluralName,
|
|
469
|
+
mapDefinition: `const {${relatedModel.itemsHook.map.name}: ${relatedModel.internalPluralName}} = ${relatedModel.itemsHook.name}()`,
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
return relatedModels;
|
|
473
|
+
}
|
|
474
|
+
function toRoleExpression(policy, userRolesVariableName) {
|
|
475
|
+
if (policy.allowAll) {
|
|
476
|
+
return 'true';
|
|
477
|
+
}
|
|
478
|
+
return `hasAnyRole(${userRolesVariableName}, ${JSON.stringify(policy.roles)})`;
|
|
479
|
+
}
|
|
480
|
+
function buildCommentCountState(showComments) {
|
|
481
|
+
if (!showComments) {
|
|
482
|
+
return '';
|
|
483
|
+
}
|
|
484
|
+
return 'const [commentCount, setCommentCount] = useState(0)';
|
|
485
|
+
}
|
|
486
|
+
function buildModelHookMutationFields(model) {
|
|
487
|
+
const fields = [];
|
|
488
|
+
if (model.itemsHook.updateField) {
|
|
489
|
+
fields.push(`${model.itemsHook.updateField.name}: updateFieldMutation,`);
|
|
490
|
+
}
|
|
491
|
+
if (model.itemsHook.updateFieldMany) {
|
|
492
|
+
fields.push(`${model.itemsHook.updateFieldMany.name}: updateFieldManyMutation,`);
|
|
493
|
+
}
|
|
494
|
+
if (model.itemsHook.deleteMany) {
|
|
495
|
+
fields.push(`${model.itemsHook.deleteMany.name}: deleteManyMutation,`);
|
|
496
|
+
}
|
|
497
|
+
return fields.join('\n ');
|
|
498
|
+
}
|
|
499
|
+
function buildCommentSidebarLogic({ showComments, model, }) {
|
|
500
|
+
if (!showComments) {
|
|
501
|
+
return '';
|
|
502
|
+
}
|
|
503
|
+
return `// Comment sidebar: compute filtered entity IDs and label map from table data
|
|
504
|
+
const commentFilteredIds = useMemo(() =>
|
|
505
|
+
filteredDataList?.map(item => String(item.id)) ?? [],
|
|
506
|
+
[filteredDataList]
|
|
507
|
+
)
|
|
508
|
+
const commentLabelMap = useMemo(() =>
|
|
509
|
+
Object.fromEntries((filteredDataList ?? []).map(item => [String(item.id), String(item.${model.labelField.name})])),
|
|
510
|
+
[filteredDataList]
|
|
511
|
+
)`;
|
|
512
|
+
}
|
|
513
|
+
function buildCommentSelectedEntityId(showComments) {
|
|
514
|
+
if (!showComments) {
|
|
515
|
+
return '';
|
|
516
|
+
}
|
|
517
|
+
return `
|
|
518
|
+
let commentSelectedEntityId: string | undefined
|
|
519
|
+
if (selectedEntityId) {
|
|
520
|
+
commentSelectedEntityId = String(selectedEntityId)
|
|
521
|
+
} else if (selectedRowIds.length === 1) {
|
|
522
|
+
commentSelectedEntityId = selectedRowIds[0]
|
|
523
|
+
}`;
|
|
524
|
+
}
|
|
525
|
+
function buildCellUpdateHandler(model) {
|
|
526
|
+
if (!model.itemsHook.updateField) {
|
|
527
|
+
return '';
|
|
528
|
+
}
|
|
529
|
+
const flushMutations = model.itemsHook.updateFieldMany
|
|
530
|
+
? `if (entry.ids.length === 1) {
|
|
531
|
+
void updateFieldMutation({ id: entry.ids[0], field: entry.field, value: entry.value } as Parameters<typeof updateFieldMutation>[0])
|
|
532
|
+
} else {
|
|
533
|
+
void updateFieldManyMutation({ ids: entry.ids, field: entry.field, value: entry.value } as Parameters<typeof updateFieldManyMutation>[0])
|
|
534
|
+
}`
|
|
535
|
+
: `for (const id of entry.ids) {
|
|
536
|
+
void updateFieldMutation({ id, field: entry.field, value: entry.value } as Parameters<typeof updateFieldMutation>[0])
|
|
537
|
+
}`;
|
|
538
|
+
const dependencies = model.itemsHook.updateFieldMany
|
|
539
|
+
? '[filteredDataList, updateFieldMutation, updateFieldManyMutation]'
|
|
540
|
+
: '[filteredDataList, updateFieldMutation]';
|
|
541
|
+
return `// Batch cell updates: collect updates within the same microtask and send as one updateFieldMany
|
|
542
|
+
const pendingUpdatesRef = useRef<Map<string, { field: string; value: unknown; ids: ${model.types.id.name}[] }> | null>(null)
|
|
543
|
+
|
|
544
|
+
const handleCellUpdate = useCallback((args: { rowIndex: number; columnId: string; value: unknown }) => {
|
|
545
|
+
const row = filteredDataList?.[args.rowIndex]
|
|
546
|
+
if (!row) return
|
|
547
|
+
|
|
548
|
+
// Initialize batch map and schedule flush on first call in this microtask
|
|
549
|
+
if (!pendingUpdatesRef.current) {
|
|
550
|
+
pendingUpdatesRef.current = new Map()
|
|
551
|
+
queueMicrotask(() => {
|
|
552
|
+
const pending = pendingUpdatesRef.current
|
|
553
|
+
pendingUpdatesRef.current = null
|
|
554
|
+
if (!pending) return
|
|
555
|
+
|
|
556
|
+
for (const entry of pending.values()) {
|
|
557
|
+
${flushMutations}
|
|
558
|
+
}
|
|
559
|
+
})
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Group by field+value so identical field updates across rows get batched
|
|
563
|
+
const key = \`\${args.columnId}::\${JSON.stringify(args.value)}\`
|
|
564
|
+
const existing = pendingUpdatesRef.current.get(key)
|
|
565
|
+
if (existing) {
|
|
566
|
+
existing.ids.push(row.id)
|
|
567
|
+
} else {
|
|
568
|
+
pendingUpdatesRef.current.set(key, { field: args.columnId, value: args.value, ids: [row.id] })
|
|
569
|
+
}
|
|
570
|
+
}, ${dependencies})`;
|
|
571
|
+
}
|
|
572
|
+
function buildRemoveRowsHandler(model) {
|
|
573
|
+
if (!model.itemsHook.deleteMany) {
|
|
574
|
+
return '';
|
|
575
|
+
}
|
|
576
|
+
return `function handleRemoveRows(rows: ${model.types.id.name}[]) {
|
|
577
|
+
void deleteManyMutation(rows)
|
|
578
|
+
}`;
|
|
579
|
+
}
|
|
580
|
+
function buildCommentSidebarTab({ showComments, model }) {
|
|
581
|
+
if (!showComments) {
|
|
582
|
+
return '';
|
|
583
|
+
}
|
|
584
|
+
return `<SidebarTab
|
|
585
|
+
side="right"
|
|
586
|
+
id="comment-content"
|
|
587
|
+
icon={MessageSquareText}
|
|
588
|
+
label="Comment"
|
|
589
|
+
order={0}
|
|
590
|
+
badge={{ label: String(commentCount) }}
|
|
591
|
+
>
|
|
592
|
+
<ModelCommentContent
|
|
593
|
+
model="${model.name}"
|
|
594
|
+
filteredIds={commentFilteredIds}
|
|
595
|
+
labelMap={commentLabelMap}
|
|
596
|
+
selectedEntityId={commentSelectedEntityId}
|
|
597
|
+
onCommentCountChange={setCommentCount}
|
|
598
|
+
/>
|
|
599
|
+
</SidebarTab>`;
|
|
600
|
+
}
|
|
601
|
+
function buildRelatedTableProps(relatedModels) {
|
|
602
|
+
return relatedModels.map((rm) => `${rm.model.table.propName}={${rm.mapName}}`).join('\n ');
|
|
603
|
+
}
|
|
604
|
+
function buildAddRowProp(model) {
|
|
605
|
+
if (model.isReadonly) {
|
|
606
|
+
return '';
|
|
607
|
+
}
|
|
608
|
+
return 'handleAddRow={() => setIsCreateModalOpen(true)}';
|
|
609
|
+
}
|
|
610
|
+
function buildCellUpdateProp(model) {
|
|
611
|
+
if (!model.itemsHook.updateField) {
|
|
612
|
+
return '';
|
|
613
|
+
}
|
|
614
|
+
return 'onCellUpdate={handleCellUpdate}';
|
|
615
|
+
}
|
|
616
|
+
function buildRemoveRowsProp(model) {
|
|
617
|
+
if (!model.itemsHook.deleteMany) {
|
|
618
|
+
return '';
|
|
619
|
+
}
|
|
620
|
+
return 'handleRemoveRows={handleRemoveRows}';
|
|
621
|
+
}
|
|
622
|
+
function resolveCreateModalContent(model, imports) {
|
|
623
|
+
if (!model.isReadonly) {
|
|
624
|
+
imports
|
|
625
|
+
.addType(model.types.id)
|
|
626
|
+
.addImport({ from: Generator.toPackageName('sonner'), items: [Generator.toFunctionName('toast')] })
|
|
627
|
+
.addImport({ from: model.forms.createModal.location, items: [model.forms.createModal.name] });
|
|
628
|
+
if (model.itemsHook.updateField) {
|
|
629
|
+
imports.addImport({ from: Generator.toPackageName('react'), items: [Generator.toFunctionName('useRef')] });
|
|
630
|
+
}
|
|
631
|
+
// In case the model does not provide any explicit label field, we generally use the id field as fallback.
|
|
632
|
+
// However, in case of creating a new item, we do not have an Id yet and hence cannot show it in success and error messages.
|
|
633
|
+
const hasLabelField = model.labelField.name !== model.idField.name;
|
|
634
|
+
const variableName = hasLabelField ? `created${model.name}` : '_';
|
|
635
|
+
const label = hasLabelField ? ` "\${${variableName}.${model.labelField.name}}"` : '';
|
|
636
|
+
return {
|
|
637
|
+
hook: ` const [isCreateModalOpen, setIsCreateModalOpen] = useState(false)`,
|
|
638
|
+
intentHook: `
|
|
639
|
+
useEffect(() => {
|
|
640
|
+
if (typeof globalThis === 'undefined') {
|
|
641
|
+
return
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
const storageKey = 'pxl:create-intent-model'
|
|
645
|
+
const targetModel = '${model.name}'
|
|
646
|
+
const openFromIntent = (intentModel: unknown) => {
|
|
647
|
+
if (intentModel === targetModel) {
|
|
648
|
+
setIsCreateModalOpen(true)
|
|
649
|
+
globalThis.sessionStorage.removeItem(storageKey)
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
openFromIntent(globalThis.sessionStorage.getItem(storageKey))
|
|
654
|
+
|
|
655
|
+
const onIntent = (event: Event) => {
|
|
656
|
+
const detail = (event as CustomEvent<{ model?: string }>).detail
|
|
657
|
+
openFromIntent(detail?.model)
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
globalThis.addEventListener('pxl:create-intent', onIntent)
|
|
661
|
+
return () => {
|
|
662
|
+
globalThis.removeEventListener('pxl:create-intent', onIntent)
|
|
663
|
+
}
|
|
664
|
+
}, [])`,
|
|
665
|
+
modal: `
|
|
666
|
+
<${model.forms.createModal.name}
|
|
667
|
+
open={isCreateModalOpen}
|
|
668
|
+
onClose={() => setIsCreateModalOpen(false)}
|
|
669
|
+
onSuccess={(${variableName}) => {
|
|
670
|
+
toast.success(\`${model.userFriendlyName}${label} created successfully!\`)
|
|
671
|
+
setIsCreateModalOpen(false)
|
|
672
|
+
}}
|
|
673
|
+
onError={(${variableName}, error) => {
|
|
674
|
+
toast.error(\`Error creating ${model.userFriendlyName}${label}: \${String(error)}\`)
|
|
675
|
+
}}
|
|
676
|
+
/>`,
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
return { hook: '', intentHook: '', modal: '' };
|
|
680
|
+
}
|
|
681
|
+
function resolveSchemaRuleSet(model, context) {
|
|
682
|
+
for (const [schemaName, schemaRules] of context.schema.schemaAuth.entries()) {
|
|
683
|
+
if (schemaName === model.databaseSchema) {
|
|
684
|
+
return (schemaRules ?? {});
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
return {};
|
|
688
|
+
}
|
|
689
|
+
function resolveEffectiveRuleSet(model, context) {
|
|
690
|
+
const schemaRuleSet = resolveSchemaRuleSet(model, context);
|
|
691
|
+
const modelRuleSet = (model.auth ?? {});
|
|
692
|
+
return { ...schemaRuleSet, ...modelRuleSet };
|
|
693
|
+
}
|
|
694
|
+
function resolveWritePolicy(model, context) {
|
|
695
|
+
const effectiveRules = resolveEffectiveRuleSet(model, context);
|
|
696
|
+
const roles = effectiveRules.write?.anyRole;
|
|
697
|
+
if (roles) {
|
|
698
|
+
return { allowAll: false, roles };
|
|
699
|
+
}
|
|
700
|
+
// If create/update/delete are configured independently, use a conservative write gate:
|
|
701
|
+
// users must satisfy all configured operation role sets to see generic write UI controls.
|
|
702
|
+
const operationRoleSets = [
|
|
703
|
+
effectiveRules.create?.anyRole,
|
|
704
|
+
effectiveRules.update?.anyRole,
|
|
705
|
+
effectiveRules.delete?.anyRole,
|
|
706
|
+
]
|
|
707
|
+
.filter((entry) => !!entry && entry.length > 0)
|
|
708
|
+
.map((entry) => Array.from(new Set(entry)));
|
|
709
|
+
if (operationRoleSets.length > 0) {
|
|
710
|
+
const [firstRoleSet, ...remainingRoleSets] = operationRoleSets;
|
|
711
|
+
if (!firstRoleSet) {
|
|
712
|
+
return { allowAll: !(context.schema.auth?.defaultDeny ?? true), roles: [] };
|
|
713
|
+
}
|
|
714
|
+
const intersection = remainingRoleSets.reduce((shared, current) => shared.filter((role) => current.includes(role)), firstRoleSet);
|
|
715
|
+
return { allowAll: false, roles: intersection };
|
|
716
|
+
}
|
|
717
|
+
return { allowAll: !(context.schema.auth?.defaultDeny ?? true), roles: [] };
|
|
718
|
+
}
|
|
719
|
+
function resolveAdminUiPolicy(model, context) {
|
|
720
|
+
const effectiveRules = resolveEffectiveRuleSet(model, context);
|
|
721
|
+
const roles = effectiveRules.adminUi?.visibleFor;
|
|
722
|
+
if (roles) {
|
|
723
|
+
return { allowAll: false, roles };
|
|
724
|
+
}
|
|
725
|
+
return { allowAll: !(context.schema.auth?.defaultDeny ?? true), roles: [] };
|
|
726
|
+
}
|
|
564
727
|
//# sourceMappingURL=model-admin-page.generator.js.map
|