betterstart-cli 0.0.102 → 0.0.104

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.
Files changed (147) hide show
  1. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/index.ts +0 -5
  2. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/send-test-webhook.ts +1 -1
  3. package/dist/assets/adapters/next/templates/renderers/commands/init/banner-art.ts +7 -0
  4. package/dist/assets/adapters/next/templates/renderers/commands/init/verify-database-reachable.ts +30 -0
  5. package/dist/assets/adapters/next/templates/renderers/commands/seed/build-seed-script.ts +171 -0
  6. package/dist/assets/adapters/next/templates/renderers/generators/actions/action-helpers/build-authorship-alias-block.ts +4 -0
  7. package/dist/assets/adapters/next/templates/renderers/generators/actions/action-helpers/build-authorship-data-fields.ts +4 -0
  8. package/dist/assets/adapters/next/templates/renderers/generators/actions/action-helpers/build-format-action-date-source.ts +18 -0
  9. package/dist/assets/adapters/next/templates/renderers/generators/actions/action-helpers/build-read-select-option-type.ts +6 -0
  10. package/dist/assets/adapters/next/templates/renderers/generators/actions/action-helpers/build-relationship-data-types.ts +25 -0
  11. package/dist/assets/adapters/next/templates/renderers/generators/actions/action-helpers/build-result-mapping.ts +62 -0
  12. package/dist/assets/adapters/next/templates/renderers/generators/actions/action-helpers/generate-populate-list-rels-function.ts +50 -0
  13. package/dist/assets/adapters/next/templates/renderers/generators/actions/entity-file-contents/build-restore-many-to-many-statements.ts +27 -0
  14. package/dist/assets/adapters/next/templates/renderers/generators/actions/entity-file-contents/build-select-option-types.ts +37 -0
  15. package/dist/assets/adapters/next/templates/renderers/generators/actions/entity-file-contents/gen-bulk-sort-order-content.ts +93 -0
  16. package/dist/assets/adapters/next/templates/renderers/generators/actions/entity-file-contents/gen-create-content.ts +157 -0
  17. package/dist/assets/adapters/next/templates/renderers/generators/actions/entity-file-contents/gen-create-select-option-content.ts +134 -0
  18. package/dist/assets/adapters/next/templates/renderers/generators/actions/entity-file-contents/gen-delete-bulk-content.ts +85 -0
  19. package/dist/assets/adapters/next/templates/renderers/generators/actions/entity-file-contents/gen-delete-content.ts +66 -0
  20. package/dist/assets/adapters/next/templates/renderers/generators/actions/entity-file-contents/gen-distinct-files.ts +45 -0
  21. package/dist/assets/adapters/next/templates/renderers/generators/actions/entity-file-contents/gen-get-by-id-content.ts +55 -0
  22. package/dist/assets/adapters/next/templates/renderers/generators/actions/entity-file-contents/gen-get-by-slug-content.ts +55 -0
  23. package/dist/assets/adapters/next/templates/renderers/generators/actions/entity-file-contents/gen-get-plural-content.ts +106 -0
  24. package/dist/assets/adapters/next/templates/renderers/generators/actions/entity-file-contents/gen-get-select-options-content.ts +55 -0
  25. package/dist/assets/adapters/next/templates/renderers/generators/actions/entity-file-contents/gen-get-versions-content.ts +19 -0
  26. package/dist/assets/adapters/next/templates/renderers/generators/actions/entity-file-contents/gen-m2m-files.ts +151 -0
  27. package/dist/assets/adapters/next/templates/renderers/generators/actions/entity-file-contents/gen-restore-content.ts +123 -0
  28. package/dist/assets/adapters/next/templates/renderers/generators/actions/entity-file-contents/gen-update-content.ts +187 -0
  29. package/dist/assets/adapters/next/templates/renderers/generators/actions/entity-file-contents/generate-resolve-media-function.ts +40 -0
  30. package/dist/assets/adapters/next/templates/renderers/generators/actions/entity-file-contents/generate-slug-helpers.ts +73 -0
  31. package/dist/assets/adapters/next/templates/renderers/generators/actions/entity-file-contents/generate-version-snapshot-helpers.ts +104 -0
  32. package/dist/assets/adapters/next/templates/renderers/generators/actions/lifecycle-hook-emissions/lifecycle-context.ts +22 -0
  33. package/dist/assets/adapters/next/templates/renderers/generators/actions/lifecycle-hook-emissions/lifecycle-hook-import.ts +4 -0
  34. package/dist/assets/adapters/next/templates/renderers/generators/actions/markdown-renderer.ts +411 -0
  35. package/dist/assets/adapters/next/templates/renderers/generators/actions/select-fields-resolver.ts +99 -0
  36. package/dist/assets/adapters/next/templates/renderers/generators/actions/single-actions/generate-single-actions.ts +292 -0
  37. package/dist/assets/adapters/next/templates/renderers/generators/columns/column-defs/generate-authorship-value.ts +8 -0
  38. package/dist/assets/adapters/next/templates/renderers/generators/columns/column-defs/generate-column-def.ts +297 -0
  39. package/dist/assets/adapters/next/templates/renderers/generators/columns/column-defs/generate-first-column-def.ts +483 -0
  40. package/dist/assets/adapters/next/templates/renderers/generators/columns/column-defs/generate-image-cell-media.ts +38 -0
  41. package/dist/assets/adapters/next/templates/renderers/generators/columns/column-defs/generate-image-element.ts +22 -0
  42. package/dist/assets/adapters/next/templates/renderers/generators/columns/column-defs/generate-relationship-label.ts +26 -0
  43. package/dist/assets/adapters/next/templates/renderers/generators/columns/column-defs/generate-select-label.ts +17 -0
  44. package/dist/assets/adapters/next/templates/renderers/generators/columns/custom-cell.ts +24 -0
  45. package/dist/assets/adapters/next/templates/renderers/generators/columns/generate-columns.ts +55 -0
  46. package/dist/assets/adapters/next/templates/renderers/generators/create-page.ts +16 -0
  47. package/dist/assets/adapters/next/templates/renderers/generators/database/generate-database.ts +5 -0
  48. package/dist/assets/adapters/next/templates/renderers/generators/database/generate-junction-table.ts +27 -0
  49. package/dist/assets/adapters/next/templates/renderers/generators/database/generate-select-options-table.ts +35 -0
  50. package/dist/assets/adapters/next/templates/renderers/generators/database/generate-table-definition.ts +25 -0
  51. package/dist/assets/adapters/next/templates/renderers/generators/dev-mode-integration/build-entity-dev-mode-snippets.ts +180 -0
  52. package/dist/assets/adapters/next/templates/renderers/generators/dev-mode-integration/build-form-dev-mode-snippets.ts +97 -0
  53. package/dist/assets/adapters/next/templates/renderers/generators/dev-mode-integration/build-single-dev-mode-snippets.ts +60 -0
  54. package/dist/assets/adapters/next/templates/renderers/generators/dev-mode-integration/create-dev-mode-integration-artifact.ts +89 -0
  55. package/dist/assets/adapters/next/templates/renderers/generators/dev-mode-integration/lifecycle-hook-code.ts +55 -0
  56. package/dist/assets/adapters/next/templates/renderers/generators/edit-page.ts +33 -0
  57. package/dist/assets/adapters/next/templates/renderers/generators/email-template/generate-email-template.ts +248 -0
  58. package/dist/assets/adapters/next/templates/renderers/generators/email-template/generate-field-value.ts +31 -0
  59. package/dist/assets/adapters/next/templates/renderers/generators/emitted-helpers.ts +16 -0
  60. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx/generate-icon-postfix.ts +27 -0
  61. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx/render-boolean-field.ts +47 -0
  62. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx/render-creatable-select-field.ts +122 -0
  63. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx/render-date-field.ts +47 -0
  64. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx/render-default-field.ts +53 -0
  65. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx/render-gallery-field.ts +46 -0
  66. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx/render-group-field.ts +16 -0
  67. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx/render-icon-field.ts +31 -0
  68. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx/render-markdown-field.ts +50 -0
  69. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx/render-media-field.ts +47 -0
  70. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx/render-multi-relationship-field.ts +91 -0
  71. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx/render-multi-select-field.ts +219 -0
  72. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx/render-number-field.ts +48 -0
  73. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx/render-richtext-field.ts +50 -0
  74. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx/render-select-field.ts +61 -0
  75. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx/render-separator-field.ts +16 -0
  76. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx/render-single-relationship-field.ts +93 -0
  77. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx/render-textarea-field.ts +53 -0
  78. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx-nested/render-list-field.ts +163 -0
  79. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx-nested/render-nested-field.ts +164 -0
  80. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx-nested/render-nested-object-list-field.ts +59 -0
  81. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx-nested/render-text-input-field.ts +27 -0
  82. package/dist/assets/adapters/next/templates/renderers/generators/form/form-entity/generate-form.ts +822 -0
  83. package/dist/assets/adapters/next/templates/renderers/generators/form/form-shared/build-default-value-from-sync-functions.ts +41 -0
  84. package/dist/assets/adapters/next/templates/renderers/generators/form/form-shared/build-ui-imports.ts +58 -0
  85. package/dist/assets/adapters/next/templates/renderers/generators/form/form-shared/generate-default-value.ts +67 -0
  86. package/dist/assets/adapters/next/templates/renderers/generators/form/form-shared/generate-nested-list-default-value.ts +13 -0
  87. package/dist/assets/adapters/next/templates/renderers/generators/form/form-shared/label-with-description.ts +14 -0
  88. package/dist/assets/adapters/next/templates/renderers/generators/form/form-shared/render-tabs-trigger.ts +18 -0
  89. package/dist/assets/adapters/next/templates/renderers/generators/form/form-shared/select-read-values-expression.ts +21 -0
  90. package/dist/assets/adapters/next/templates/renderers/generators/form/form-single/build-group-creatable-select-handlers.ts +67 -0
  91. package/dist/assets/adapters/next/templates/renderers/generators/form/form-single/build-group-creatable-select-state.ts +29 -0
  92. package/dist/assets/adapters/next/templates/renderers/generators/form/form-single/build-group-field-array-hooks.ts +13 -0
  93. package/dist/assets/adapters/next/templates/renderers/generators/form/form-single/build-group-fields-jsx.ts +30 -0
  94. package/dist/assets/adapters/next/templates/renderers/generators/form/form-single/generate-card-component.ts +129 -0
  95. package/dist/assets/adapters/next/templates/renderers/generators/form/form-single/generate-single-form.ts +66 -0
  96. package/dist/assets/adapters/next/templates/renderers/generators/form-admin/admin-columns/generate-column-def.ts +72 -0
  97. package/dist/assets/adapters/next/templates/renderers/generators/form-admin/admin-columns/generate-columns.ts +89 -0
  98. package/dist/assets/adapters/next/templates/renderers/generators/form-admin/admin-page/generate-page.ts +23 -0
  99. package/dist/assets/adapters/next/templates/renderers/generators/form-admin/admin-page-content.ts +246 -0
  100. package/dist/assets/adapters/next/templates/renderers/generators/form-admin/admin-submission-drawer.ts +216 -0
  101. package/dist/assets/adapters/next/templates/renderers/generators/form-admin/admin-table.ts +273 -0
  102. package/dist/assets/adapters/next/templates/renderers/generators/form-pipeline/form-actions/exclusive-date-upper-bound-helper.ts +11 -0
  103. package/dist/assets/adapters/next/templates/renderers/generators/form-pipeline/form-actions/generate-form-actions.ts +505 -0
  104. package/dist/assets/adapters/next/templates/renderers/generators/form-pipeline/form-component-multistep/build-component-source.ts +197 -0
  105. package/dist/assets/adapters/next/templates/renderers/generators/form-pipeline/form-component-shared/build-field-error-helper.ts +16 -0
  106. package/dist/assets/adapters/next/templates/renderers/generators/form-pipeline/form-component-shared/error-message-line.ts +13 -0
  107. package/dist/assets/adapters/next/templates/renderers/generators/form-pipeline/form-component-shared/generate-field-jsx.ts +226 -0
  108. package/dist/assets/adapters/next/templates/renderers/generators/form-pipeline/form-component-shared/generate-file-upload-field-jsx.ts +41 -0
  109. package/dist/assets/adapters/next/templates/renderers/generators/form-pipeline/form-component-shared/generate-list-field-jsx.ts +160 -0
  110. package/dist/assets/adapters/next/templates/renderers/generators/form-pipeline/form-component-shared/generate-multiselect-field-jsx.ts +40 -0
  111. package/dist/assets/adapters/next/templates/renderers/generators/form-pipeline/form-component-shared/generate-number-field-jsx.ts +41 -0
  112. package/dist/assets/adapters/next/templates/renderers/generators/form-pipeline/form-component-shared/generate-text-field-jsx.ts +39 -0
  113. package/dist/assets/adapters/next/templates/renderers/generators/form-pipeline/form-component-shared/label-with-description-line.ts +14 -0
  114. package/dist/assets/adapters/next/templates/renderers/generators/form-pipeline/form-component-single/build-single-step-form-component-source.ts +100 -0
  115. package/dist/assets/adapters/next/templates/renderers/generators/form-pipeline/form-database.ts +29 -0
  116. package/dist/assets/adapters/next/templates/renderers/generators/form-pipeline/form-hook.ts +142 -0
  117. package/dist/assets/adapters/next/templates/renderers/generators/hook/generate-hook.ts +169 -0
  118. package/dist/assets/adapters/next/templates/renderers/generators/hook/generate-single-hook.ts +30 -0
  119. package/dist/assets/adapters/next/templates/renderers/generators/navigation-extract.ts +19 -0
  120. package/dist/assets/adapters/next/templates/renderers/generators/page-content/generate-page-content.ts +419 -0
  121. package/dist/assets/adapters/next/templates/renderers/generators/page.ts +24 -0
  122. package/dist/assets/adapters/next/templates/renderers/generators/row-actions-emitters/build-delete-dialog-source.ts +108 -0
  123. package/dist/assets/adapters/next/templates/renderers/generators/row-actions-emitters/build-row-actions-source.ts +61 -0
  124. package/dist/assets/adapters/next/templates/renderers/generators/single-page.ts +59 -0
  125. package/dist/assets/adapters/next/templates/renderers/generators/sortable-header/build-sort-label-expr.ts +9 -0
  126. package/dist/assets/adapters/next/templates/renderers/generators/sortable-header/build-sortable-header-button-jsx.ts +20 -0
  127. package/dist/assets/adapters/next/templates/renderers/generators/sortable-header/build-sortable-header-def.ts +14 -0
  128. package/dist/assets/adapters/next/templates/renderers/generators/table.ts +284 -0
  129. package/dist/assets/adapters/next/templates/renderers/generators/thankyou-email-template.ts +110 -0
  130. package/dist/assets/adapters/next/templates/renderers/generators/webhook-events/render-extract.ts +14 -0
  131. package/dist/assets/adapters/next/templates/renderers/init/scaffolders/next-config/create-next-config-template.ts +48 -0
  132. package/dist/assets/adapters/next/templates/renderers/init/scaffolders/tailwind/admin-shadow-input-utility.ts +9 -0
  133. package/dist/assets/adapters/next/templates/renderers/init/scaffolders/tailwind/admin-theme-block.ts +42 -0
  134. package/dist/assets/adapters/next/templates/renderers/init/templates/app/admin.ts +12 -0
  135. package/dist/assets/adapters/next/templates/renderers/integration-runtime/render-core-email-provider.ts +27 -0
  136. package/dist/assets/adapters/next/templates/renderers/integration-runtime/render-core-storage-provider.ts +27 -0
  137. package/dist/assets/adapters/next/templates/renderers/snapshots/barrels/render-db-barrel.ts +8 -0
  138. package/dist/assets/adapters/next/templates/renderers/snapshots/barrels/render-navigation-barrel.ts +63 -0
  139. package/dist/assets/adapters/next/templates/renderers/snapshots/barrels/render-webhook-events-barrel.ts +16 -0
  140. package/dist/{chunk-TIJEHWRF.js → chunk-OYU73Q2W.js} +1 -50
  141. package/dist/chunk-OYU73Q2W.js.map +1 -0
  142. package/dist/cli.js +8903 -4025
  143. package/dist/cli.js.map +1 -1
  144. package/dist/{read-namespaced-template-UXU7OCY3.js → read-namespaced-template-EIH2NPDJ.js} +2 -2
  145. package/package.json +1 -1
  146. package/dist/chunk-TIJEHWRF.js.map +0 -1
  147. /package/dist/{read-namespaced-template-UXU7OCY3.js.map → read-namespaced-template-EIH2NPDJ.js.map} +0 -0
@@ -0,0 +1,157 @@
1
+ export function renderGenCreateContentServerActionTemplate({
2
+ ctxTableVar,
3
+ ctxCacheInvalidationFn,
4
+ ctxCamelPlural,
5
+ ctxSingular,
6
+ kebabSingular,
7
+ snapshotImport,
8
+ lifecycleHookImport,
9
+ markdownImport,
10
+ createContext,
11
+ beforeHook,
12
+ createBeforePublishBlock,
13
+ slugSourceValue,
14
+ toKebabCase,
15
+ ctxHtmlCreateBlock,
16
+ ctxCreateMappings,
17
+ ctxHasHtmlOutputContent,
18
+ ctxHasDraftContent,
19
+ ctxPlural,
20
+ snapshotData,
21
+ ctxSingularText,
22
+ createResultContext,
23
+ afterHook,
24
+ createAfterPublishBlock,
25
+ ctxCamelSingular
26
+ }: {
27
+ ctxTableVar: string
28
+ ctxCacheInvalidationFn: string
29
+ ctxCamelPlural: string
30
+ ctxSingular: string
31
+ kebabSingular: string
32
+ snapshotImport: string
33
+ lifecycleHookImport: string
34
+ markdownImport: string
35
+ createContext: string
36
+ beforeHook: string
37
+ createBeforePublishBlock: string
38
+ slugSourceValue: string
39
+ toKebabCase: string
40
+ ctxHtmlCreateBlock: string
41
+ ctxCreateMappings: string
42
+ ctxHasHtmlOutputContent: string
43
+ ctxHasDraftContent: string
44
+ ctxPlural: string
45
+ snapshotData: string
46
+ ctxSingularText: string
47
+ createResultContext: string
48
+ afterHook: string
49
+ createAfterPublishBlock: string
50
+ ctxCamelSingular: string
51
+ }): string {
52
+ return `'use server'
53
+
54
+ import db from '@admin/db'
55
+ import { requireRole, UserRole } from '@admin/auth/middleware'
56
+ import { ${ctxTableVar} } from '@admin/db/schema'
57
+ import { desc } from 'drizzle-orm'
58
+ import { ${ctxCacheInvalidationFn} } from 'next/cache'
59
+ import { createEntityVersion, entityVersionsCacheTags } from '@admin/actions/entity-versions'
60
+ import { ${ctxCamelPlural}CacheTags } from './types'
61
+ import { get${ctxSingular}ById } from './get-${kebabSingular}-by-id'
62
+ import { getTouchedFieldNames } from '@admin/utils/audit/get-touched-field-names'
63
+ import {
64
+ get${ctxSingular}SlugRetryLimit,
65
+ is${ctxSingular}SlugConflict,
66
+ resolveUnique${ctxSingular}Slug,
67
+ ${snapshotImport}
68
+ } from './helpers'
69
+ ${lifecycleHookImport}
70
+ import type { ${ctxSingular}CreateInput, ${ctxSingular}CreateResult, ${ctxSingular} } from './types'${markdownImport}
71
+
72
+ export async function create${ctxSingular}(input: ${ctxSingular}CreateInput): Promise<${ctxSingular}CreateResult> {
73
+ const actor = await requireRole([UserRole.ADMIN, UserRole.EDITOR])
74
+
75
+ try {
76
+ const actorId = actor.id
77
+ const now = new Date().toISOString()
78
+ const auditRequestMetadata = await getAuditRequestMetadata()
79
+ const changedFields = getTouchedFieldNames(input as unknown as Record<string, unknown>)
80
+ const lifecycleContext = ${createContext}
81
+ ${beforeHook}
82
+ ${createBeforePublishBlock}
83
+
84
+ let result: (typeof ${ctxTableVar}.$inferSelect)[] | null = null
85
+ for (let attempt = 0; attempt < get${ctxSingular}SlugRetryLimit(); attempt += 1) {
86
+ try {
87
+ result = await db.transaction(async (tx) => {
88
+ const maxSortOrderResult = await tx
89
+ .select({ maxOrder: ${ctxTableVar}.sortOrder })
90
+ .from(${ctxTableVar})
91
+ .orderBy(desc(${ctxTableVar}.sortOrder))
92
+ .limit(1)
93
+ const nextSortOrder = (maxSortOrderResult[0]?.maxOrder ?? 0) + 1
94
+ const resolvedSlug = await resolveUnique${ctxSingular}Slug(tx, {
95
+ requestedSlug: input.slug,
96
+ sourceValue: ${slugSourceValue},
97
+ fallbackPrefix: '${toKebabCase}'
98
+ })
99
+ ${ctxHtmlCreateBlock}
100
+ const createdRows = await tx.insert(${ctxTableVar}).values({
101
+ ${ctxCreateMappings},${ctxHasHtmlOutputContent}${ctxHasDraftContent}
102
+ sortOrder: nextSortOrder,
103
+ createdAt: now,
104
+ updatedAt: now,
105
+ createdBy: actorId,
106
+ updatedBy: actorId
107
+ }).returning()
108
+
109
+ const createdRow = createdRows[0]
110
+ if (createdRow?.id) {
111
+ await createEntityVersion(tx, {
112
+ entityType: '${ctxPlural}',
113
+ entityId: createdRow.id,
114
+ data: ${snapshotData},
115
+ createdBy: actorId
116
+ })
117
+ }
118
+
119
+ return createdRows
120
+ })
121
+ break
122
+ } catch (error) {
123
+ if (is${ctxSingular}SlugConflict(error) && attempt < get${ctxSingular}SlugRetryLimit() - 1) {
124
+ continue
125
+ }
126
+ throw error
127
+ }
128
+ }
129
+
130
+ if (!result) throw new Error('Failed to create ${ctxSingularText}')
131
+
132
+ const created${ctxSingular}Id = result[0]?.id
133
+ ${ctxCacheInvalidationFn}(${ctxCamelPlural}CacheTags.all)
134
+ if (created${ctxSingular}Id !== null && created${ctxSingular}Id !== undefined) {
135
+ ${ctxCacheInvalidationFn}(${ctxCamelPlural}CacheTags.byId(created${ctxSingular}Id))
136
+ ${ctxCacheInvalidationFn}(entityVersionsCacheTags.for('${ctxPlural}', created${ctxSingular}Id))
137
+ }
138
+ const created${ctxSingular} =
139
+ typeof created${ctxSingular}Id === 'string'
140
+ ? await get${ctxSingular}ById(created${ctxSingular}Id)
141
+ : null
142
+ const created${ctxSingular}Data = created${ctxSingular} ?? (result[0] as unknown as ${ctxSingular})
143
+ const created${ctxSingular}Context = ${createResultContext}
144
+ ${afterHook}
145
+ ${createAfterPublishBlock}
146
+
147
+ return {
148
+ success: true,
149
+ ${ctxCamelSingular}: created${ctxSingular}Data
150
+ }
151
+ } catch (error) {
152
+ console.error('Error creating ${ctxSingularText}:', error)
153
+ throw new Error(error instanceof Error ? error.message : 'Failed to create ${ctxSingularText}')
154
+ }
155
+ }
156
+ `
157
+ }
@@ -0,0 +1,134 @@
1
+ export function renderGenCreateSelectOptionContentServerActionTemplate({
2
+ ctxSelectOptionsTableVar,
3
+ ctxCacheInvalidationFn,
4
+ ctxCamelSingular,
5
+ ctxSingular,
6
+ selectOptionStaticOptions,
7
+ ctxSingularText
8
+ }: {
9
+ ctxSelectOptionsTableVar: string
10
+ ctxCacheInvalidationFn: string
11
+ ctxCamelSingular: string
12
+ ctxSingular: string
13
+ selectOptionStaticOptions: string
14
+ ctxSingularText: string
15
+ }): string {
16
+ return `'use server'
17
+
18
+ import { requireRole, UserRole } from '@admin/auth/middleware'
19
+ import db from '@admin/db'
20
+ import { ${ctxSelectOptionsTableVar} } from '@admin/db/schema'
21
+ import { eq } from 'drizzle-orm'
22
+ import { ${ctxCacheInvalidationFn} } from 'next/cache'
23
+ import { ${ctxCamelSingular}SelectOptionsCacheTags } from './types'
24
+ import type {
25
+ Create${ctxSingular}SelectOptionInput,
26
+ Create${ctxSingular}SelectOptionResult,
27
+ ${ctxSingular}SelectOption,
28
+ ${ctxSingular}SelectOptionFieldName
29
+ } from './types'
30
+
31
+ ${selectOptionStaticOptions}
32
+
33
+ function toSelectOptionValue(label: string): string {
34
+ const normalized = label
35
+ .trim()
36
+ .toLowerCase()
37
+ .replace(/[^a-z0-9]+/g, '-')
38
+ .replace(/^-+|-+$/g, '')
39
+
40
+ return normalized || 'option'
41
+ }
42
+
43
+ async function resolveSelectOptionValue(
44
+ fieldName: ${ctxSingular}SelectOptionFieldName,
45
+ label: string
46
+ ): Promise<string> {
47
+ const baseValue = toSelectOptionValue(label)
48
+ const rows = await db
49
+ .select({ value: ${ctxSelectOptionsTableVar}.value })
50
+ .from(${ctxSelectOptionsTableVar})
51
+ .where(eq(${ctxSelectOptionsTableVar}.fieldName, fieldName))
52
+ const usedValues = new Set([
53
+ ...STATIC_SELECT_OPTIONS[fieldName].map((option) => option.value),
54
+ ...rows.map((row) => row.value)
55
+ ])
56
+
57
+ if (!usedValues.has(baseValue)) return baseValue
58
+
59
+ let suffix = 2
60
+ let value = \`\${baseValue}-\${suffix}\`
61
+ while (usedValues.has(value)) {
62
+ suffix += 1
63
+ value = \`\${baseValue}-\${suffix}\`
64
+ }
65
+
66
+ return value
67
+ }
68
+
69
+ export async function create${ctxSingular}SelectOption(
70
+ input: Create${ctxSingular}SelectOptionInput
71
+ ): Promise<Create${ctxSingular}SelectOptionResult> {
72
+ const actor = await requireRole([UserRole.ADMIN, UserRole.EDITOR])
73
+
74
+ try {
75
+ const label = input.label.trim()
76
+ if (!label) {
77
+ return { success: false, error: 'Option label is required' }
78
+ }
79
+
80
+ const staticMatch = STATIC_SELECT_OPTIONS[input.fieldName].find(
81
+ (option) => option.label.toLowerCase() === label.toLowerCase()
82
+ )
83
+ if (staticMatch) {
84
+ return {
85
+ success: true,
86
+ option: {
87
+ id: staticMatch.value,
88
+ fieldName: input.fieldName,
89
+ label: staticMatch.label,
90
+ value: staticMatch.value,
91
+ createdAt: '',
92
+ updatedAt: ''
93
+ }
94
+ }
95
+ }
96
+
97
+ const rows = await db
98
+ .select()
99
+ .from(${ctxSelectOptionsTableVar})
100
+ .where(eq(${ctxSelectOptionsTableVar}.fieldName, input.fieldName))
101
+ const existing = rows.find((row) => row.label.toLowerCase() === label.toLowerCase())
102
+ if (existing) {
103
+ return { success: true, option: existing as ${ctxSingular}SelectOption }
104
+ }
105
+
106
+ const actorId = actor.id
107
+ const now = new Date().toISOString()
108
+ const value = await resolveSelectOptionValue(input.fieldName, label)
109
+ const [option] = await db
110
+ .insert(${ctxSelectOptionsTableVar})
111
+ .values({
112
+ fieldName: input.fieldName,
113
+ label,
114
+ value,
115
+ createdAt: now,
116
+ updatedAt: now,
117
+ createdBy: actorId,
118
+ updatedBy: actorId
119
+ })
120
+ .returning()
121
+
122
+ ${ctxCacheInvalidationFn}(${ctxCamelSingular}SelectOptionsCacheTags.byField(input.fieldName))
123
+
124
+ return { success: true, option: option as ${ctxSingular}SelectOption }
125
+ } catch (error) {
126
+ console.error('Error creating ${ctxSingularText} select option:', error)
127
+ return {
128
+ success: false,
129
+ error: error instanceof Error ? error.message : 'Failed to create option'
130
+ }
131
+ }
132
+ }
133
+ `
134
+ }
@@ -0,0 +1,85 @@
1
+ export function renderGenDeleteBulkContentServerActionTemplate({
2
+ ctxTableVar,
3
+ ctxCacheInvalidationFn,
4
+ ctxCamelPlural,
5
+ lifecycleHookImport,
6
+ ctxSingular,
7
+ ctxPlural,
8
+ ctxPluralText,
9
+ beforeHook,
10
+ afterHook
11
+ }: {
12
+ ctxTableVar: string
13
+ ctxCacheInvalidationFn: string
14
+ ctxCamelPlural: string
15
+ lifecycleHookImport: string
16
+ ctxSingular: string
17
+ ctxPlural: string
18
+ ctxPluralText: string
19
+ beforeHook: string
20
+ afterHook: string
21
+ }): string {
22
+ return `'use server'
23
+
24
+ import db from '@admin/db'
25
+ import { requireRole, UserRole } from '@admin/auth/middleware'
26
+ import { ${ctxTableVar} } from '@admin/db/schema'
27
+ import { inArray } from 'drizzle-orm'
28
+ import { ${ctxCacheInvalidationFn} } from 'next/cache'
29
+ import { deleteEntityVersions, entityVersionsCacheTags } from '@admin/actions/entity-versions'
30
+ import { ${ctxCamelPlural}CacheTags } from './types'
31
+ ${lifecycleHookImport}
32
+ import type { ${ctxSingular}DeleteResult } from './types'
33
+
34
+ export async function deleteBulk${ctxPlural}(ids: string[]): Promise<${ctxSingular}DeleteResult> {
35
+ const actor = await requireRole([UserRole.ADMIN, UserRole.EDITOR])
36
+ if (ids.length === 0) return { success: false, error: 'No items selected for deletion' }
37
+
38
+ const now = new Date().toISOString()
39
+ const auditRequestMetadata = await getAuditRequestMetadata()
40
+ const lifecycleContexts = ids.map((id) => ({
41
+ user: actor,
42
+ entityType: '${ctxPluralText}',
43
+ timestamp: now,
44
+ sourceType: 'admin_ui' as const,
45
+ ipAddress: auditRequestMetadata.ipAddress,
46
+ userAgent: auditRequestMetadata.userAgent,
47
+ entityId: id,
48
+ changedFields: []
49
+ }))
50
+
51
+ for (const lifecycleContext of lifecycleContexts) {
52
+ ${beforeHook}
53
+ }
54
+
55
+ try {
56
+ const deletedRows = await db.transaction(async (tx) => {
57
+ await deleteEntityVersions(tx, '${ctxPluralText}', ids)
58
+ return tx.delete(${ctxTableVar}).where(inArray(${ctxTableVar}.id, ids)).returning()
59
+ })
60
+ const deletedRowsById = new Map(
61
+ deletedRows.map((row) => [row.id, row as unknown as Record<string, unknown>])
62
+ )
63
+ ${ctxCacheInvalidationFn}(${ctxCamelPlural}CacheTags.all)
64
+ for (const id of ids) {
65
+ ${ctxCacheInvalidationFn}(${ctxCamelPlural}CacheTags.byId(id))
66
+ ${ctxCacheInvalidationFn}(entityVersionsCacheTags.for('${ctxPluralText}', id))
67
+ }
68
+ for (const lifecycleContext of lifecycleContexts) {
69
+ ${afterHook}
70
+ }
71
+ return { success: true }
72
+ } catch (error) {
73
+ console.error('Error deleting ${ctxPluralText}:', error)
74
+ return { success: false, error: error instanceof Error ? error.message : 'Failed to delete ${ctxPluralText}' }
75
+ }
76
+ }
77
+ `
78
+ }
79
+
80
+ export function renderGenDeleteBulkContentTemplate(): string {
81
+ return `{
82
+ ...lifecycleContext,
83
+ data: deletedRowsById.get(lifecycleContext.entityId)
84
+ }`
85
+ }
@@ -0,0 +1,66 @@
1
+ export function renderGenDeleteContentServerActionTemplate({
2
+ ctxTableVar,
3
+ ctxCacheInvalidationFn,
4
+ ctxCamelPlural,
5
+ lifecycleHookImport,
6
+ ctxSingular,
7
+ deleteContext,
8
+ beforeHook,
9
+ ctxPlural,
10
+ afterHook,
11
+ ctxSingularText
12
+ }: {
13
+ ctxTableVar: string
14
+ ctxCacheInvalidationFn: string
15
+ ctxCamelPlural: string
16
+ lifecycleHookImport: string
17
+ ctxSingular: string
18
+ deleteContext: string
19
+ beforeHook: string
20
+ ctxPlural: string
21
+ afterHook: string
22
+ ctxSingularText: string
23
+ }): string {
24
+ return `'use server'
25
+
26
+ import db from '@admin/db'
27
+ import { requireRole, UserRole } from '@admin/auth/middleware'
28
+ import { ${ctxTableVar} } from '@admin/db/schema'
29
+ import { eq } from 'drizzle-orm'
30
+ import { ${ctxCacheInvalidationFn} } from 'next/cache'
31
+ import { deleteEntityVersions, entityVersionsCacheTags } from '@admin/actions/entity-versions'
32
+ import { ${ctxCamelPlural}CacheTags } from './types'
33
+ ${lifecycleHookImport}
34
+ import type { ${ctxSingular}DeleteResult } from './types'
35
+
36
+ export async function delete${ctxSingular}(id: string): Promise<${ctxSingular}DeleteResult> {
37
+ const actor = await requireRole([UserRole.ADMIN, UserRole.EDITOR])
38
+ const now = new Date().toISOString()
39
+ const auditRequestMetadata = await getAuditRequestMetadata()
40
+ const lifecycleContext = ${deleteContext}
41
+ ${beforeHook}
42
+
43
+ try {
44
+ const [deletedRow] = await db.transaction(async (tx) => {
45
+ await deleteEntityVersions(tx, '${ctxPlural}', [id])
46
+ return tx.delete(${ctxTableVar}).where(eq(${ctxTableVar}.id, id)).returning()
47
+ })
48
+ ${ctxCacheInvalidationFn}(${ctxCamelPlural}CacheTags.all)
49
+ ${ctxCacheInvalidationFn}(${ctxCamelPlural}CacheTags.byId(id))
50
+ ${ctxCacheInvalidationFn}(entityVersionsCacheTags.for('${ctxPlural}', id))
51
+ ${afterHook}
52
+ return { success: true }
53
+ } catch (error) {
54
+ console.error('Error deleting ${ctxSingularText}:', error)
55
+ return { success: false, error: error instanceof Error ? error.message : 'Failed to delete ${ctxSingularText}' }
56
+ }
57
+ }
58
+ `
59
+ }
60
+
61
+ export function renderGenDeleteContentTemplate(): string {
62
+ return `{
63
+ ...lifecycleContext,
64
+ data: deletedRow as unknown as Record<string, unknown> | undefined
65
+ }`
66
+ }
@@ -0,0 +1,45 @@
1
+ export function renderGenDistinctFilesServerActionTemplate({
2
+ ctxTableVar,
3
+ ctxCacheReadImport,
4
+ ctxCamelPlural,
5
+ ctxPlural,
6
+ fieldPascal,
7
+ ctxCacheLifeLine,
8
+ filterField,
9
+ ctxPluralText
10
+ }: {
11
+ ctxTableVar: string
12
+ ctxCacheReadImport: string
13
+ ctxCamelPlural: string
14
+ ctxPlural: string
15
+ fieldPascal: string
16
+ ctxCacheLifeLine: string
17
+ filterField: string
18
+ ctxPluralText: string
19
+ }): string {
20
+ return `'use server'
21
+
22
+ import db from '@admin/db'
23
+ import { ${ctxTableVar} } from '@admin/db/schema'
24
+ import { asc, isNotNull } from 'drizzle-orm'
25
+ ${ctxCacheReadImport}
26
+ import { ${ctxCamelPlural}CacheTags } from './types'
27
+
28
+ export async function getDistinct${ctxPlural}${fieldPascal}(): Promise<string[]> {
29
+ 'use cache'${ctxCacheLifeLine}
30
+ cacheTag(${ctxCamelPlural}CacheTags.all)
31
+
32
+ try {
33
+ const results = await db
34
+ .selectDistinct({ value: ${ctxTableVar}.${filterField} })
35
+ .from(${ctxTableVar})
36
+ .where(isNotNull(${ctxTableVar}.${filterField}))
37
+ .orderBy(asc(${ctxTableVar}.${filterField}))
38
+ return results.map((r) => String(r.value)).filter(Boolean)
39
+ } catch (error) {
40
+ console.error('Error fetching distinct ${ctxPluralText} ${filterField}:', error)
41
+ return []
42
+ }
43
+ }
44
+ `
45
+ }
@@ -0,0 +1,55 @@
1
+ export function renderGenGetByIdContentServerActionTemplate({
2
+ join,
3
+ joinText,
4
+ ctxCacheReadImport,
5
+ ctxCamelPlural,
6
+ ctxSingular,
7
+ populateImport,
8
+ authorshipAliasBlock,
9
+ ctxCacheLifeLine,
10
+ ctxSelectClause,
11
+ ctxTableVar,
12
+ ctxSingleRowReturn,
13
+ ctxSingularText
14
+ }: {
15
+ join: string
16
+ joinText: string
17
+ ctxCacheReadImport: string
18
+ ctxCamelPlural: string
19
+ ctxSingular: string
20
+ populateImport: string
21
+ authorshipAliasBlock: string
22
+ ctxCacheLifeLine: string
23
+ ctxSelectClause: string
24
+ ctxTableVar: string
25
+ ctxSingleRowReturn: string
26
+ ctxSingularText: string
27
+ }): string {
28
+ return `'use server'
29
+
30
+ import db from '@admin/db'
31
+ import { ${join} } from '@admin/db/schema'
32
+ import { ${joinText} } from 'drizzle-orm'
33
+ import { alias } from 'drizzle-orm/pg-core'
34
+ ${ctxCacheReadImport}
35
+ import { ${ctxCamelPlural}CacheTags } from './types'
36
+ import type { ${ctxSingular} } from './types'${populateImport}
37
+
38
+ ${authorshipAliasBlock}
39
+
40
+ export async function get${ctxSingular}ById(id: string): Promise<${ctxSingular} | null> {
41
+ 'use cache'${ctxCacheLifeLine}
42
+ cacheTag(${ctxCamelPlural}CacheTags.byId(id))
43
+ cacheTag(${ctxCamelPlural}CacheTags.all)
44
+
45
+ try {
46
+ const result = await ${ctxSelectClause}.where(eq(${ctxTableVar}.id, id)).limit(1)
47
+ if (result.length === 0) return null
48
+ ${ctxSingleRowReturn}
49
+ } catch (error) {
50
+ console.error('Error fetching ${ctxSingularText}:', error)
51
+ throw new Error('Failed to fetch ${ctxSingularText}')
52
+ }
53
+ }
54
+ `
55
+ }
@@ -0,0 +1,55 @@
1
+ export function renderGenGetBySlugContentServerActionTemplate({
2
+ join,
3
+ joinText,
4
+ ctxCacheReadImport,
5
+ ctxCamelPlural,
6
+ ctxSingular,
7
+ populateImport,
8
+ authorshipAliasBlock,
9
+ ctxCacheLifeLine,
10
+ ctxSelectClause,
11
+ ctxTableVar,
12
+ ctxSingleRowReturn,
13
+ ctxSingularText
14
+ }: {
15
+ join: string
16
+ joinText: string
17
+ ctxCacheReadImport: string
18
+ ctxCamelPlural: string
19
+ ctxSingular: string
20
+ populateImport: string
21
+ authorshipAliasBlock: string
22
+ ctxCacheLifeLine: string
23
+ ctxSelectClause: string
24
+ ctxTableVar: string
25
+ ctxSingleRowReturn: string
26
+ ctxSingularText: string
27
+ }): string {
28
+ return `'use server'
29
+
30
+ import db from '@admin/db'
31
+ import { ${join} } from '@admin/db/schema'
32
+ import { ${joinText} } from 'drizzle-orm'
33
+ import { alias } from 'drizzle-orm/pg-core'
34
+ ${ctxCacheReadImport}
35
+ import { ${ctxCamelPlural}CacheTags } from './types'
36
+ import type { ${ctxSingular} } from './types'${populateImport}
37
+
38
+ ${authorshipAliasBlock}
39
+
40
+ export async function get${ctxSingular}BySlug(slug: string): Promise<${ctxSingular} | null> {
41
+ 'use cache'${ctxCacheLifeLine}
42
+ cacheTag(${ctxCamelPlural}CacheTags.bySlug(slug))
43
+ cacheTag(${ctxCamelPlural}CacheTags.all)
44
+
45
+ try {
46
+ const result = await ${ctxSelectClause}.where(eq(${ctxTableVar}.slug, slug)).limit(1)
47
+ if (result.length === 0) return null
48
+ ${ctxSingleRowReturn}
49
+ } catch (error) {
50
+ console.error('Error fetching ${ctxSingularText} by slug:', error)
51
+ throw new Error('Failed to fetch ${ctxSingularText} by slug')
52
+ }
53
+ }
54
+ `
55
+ }
@@ -0,0 +1,106 @@
1
+ export function renderGenGetPluralContentServerActionTemplate({
2
+ join,
3
+ joinText,
4
+ ctxCacheReadImport,
5
+ ctxCamelPlural,
6
+ sort,
7
+ populateImport,
8
+ dateRangeHelper,
9
+ authorshipAliasBlock,
10
+ columnMapEntries,
11
+ ctxPlural,
12
+ ctxCacheLifeLine,
13
+ ctxSearchBlock,
14
+ ctxFilterConditions,
15
+ ctxListSelectClause,
16
+ ctxTableVar,
17
+ ctxWhere,
18
+ ctxListResultMapping,
19
+ ctxPluralText
20
+ }: {
21
+ join: string
22
+ joinText: string
23
+ ctxCacheReadImport: string
24
+ ctxCamelPlural: string
25
+ sort: string
26
+ populateImport: string
27
+ dateRangeHelper: string
28
+ authorshipAliasBlock: string
29
+ columnMapEntries: string
30
+ ctxPlural: string
31
+ ctxCacheLifeLine: string
32
+ ctxSearchBlock: string
33
+ ctxFilterConditions: string
34
+ ctxListSelectClause: string
35
+ ctxTableVar: string
36
+ ctxWhere: string
37
+ ctxListResultMapping: string
38
+ ctxPluralText: string
39
+ }): string {
40
+ return `'use server'
41
+
42
+ import db from '@admin/db'
43
+ import { ${join} } from '@admin/db/schema'
44
+ import { ${joinText} } from 'drizzle-orm'
45
+ import { alias } from 'drizzle-orm/pg-core'
46
+ ${ctxCacheReadImport}
47
+ import { ${ctxCamelPlural}CacheTags } from './types'
48
+ import type { ${sort} } from './types'${populateImport}${dateRangeHelper}
49
+
50
+ ${authorshipAliasBlock}
51
+
52
+ const SORTABLE_COLUMNS = {
53
+ ${columnMapEntries}
54
+ } as const
55
+
56
+ export async function get${ctxPlural}(query?: ${ctxPlural}Query): Promise<${ctxPlural}Page> {
57
+ 'use cache'${ctxCacheLifeLine}
58
+ cacheTag(${ctxCamelPlural}CacheTags.all)
59
+
60
+ try {
61
+ const conditions = []
62
+ ${ctxSearchBlock}${ctxFilterConditions}
63
+
64
+ const baseQuery = ${ctxListSelectClause}
65
+
66
+ const sortColumn = query?.orderBy && query.orderBy in SORTABLE_COLUMNS
67
+ ? SORTABLE_COLUMNS[query.orderBy as keyof typeof SORTABLE_COLUMNS]
68
+ : ${ctxTableVar}.sortOrder
69
+ const sortFn = query?.orderDirection === 'desc' ? desc : asc
70
+ const orderClause = sortFn(sortColumn)
71
+
72
+ const orderedQuery = conditions.length > 0
73
+ ? baseQuery.where(and(...conditions)).orderBy(orderClause)
74
+ : baseQuery.orderBy(orderClause)
75
+
76
+ const rawLimit = query?.limit
77
+ const limit = typeof rawLimit === 'number'
78
+ ? Number.isFinite(rawLimit)
79
+ ? Math.max(1, rawLimit)
80
+ : 1
81
+ : undefined
82
+ const rawOffset = query?.offset
83
+ const offset = typeof rawOffset === 'number' && Number.isFinite(rawOffset)
84
+ ? Math.max(0, rawOffset)
85
+ : 0
86
+ const isPaginated = typeof limit === 'number'
87
+ let results: unknown[]
88
+ if (isPaginated) {
89
+ const paginatedQuery = offset > 0
90
+ ? orderedQuery.limit(limit).offset(offset)
91
+ : orderedQuery.limit(limit)
92
+ results = await paginatedQuery
93
+ } else {
94
+ results = await orderedQuery
95
+ }
96
+
97
+ const total = isPaginated
98
+ ? Number((await db.select({ count: sql\`count(*)\` }).from(${ctxTableVar})${ctxWhere})[0]?.count ?? 0)
99
+ : results.length${ctxListResultMapping}
100
+ } catch (error) {
101
+ console.error('Error fetching ${ctxPluralText}:', error)
102
+ throw new Error('Failed to fetch ${ctxPluralText}')
103
+ }
104
+ }
105
+ `
106
+ }