nextjs-cms 0.5.9 → 0.5.11

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 (258) hide show
  1. package/dist/api/axios/axiosInstance.d.ts +1 -1
  2. package/dist/api/axios/axiosInstance.js +8 -8
  3. package/dist/api/index.d.ts +855 -855
  4. package/dist/api/index.d.ts.map +1 -1
  5. package/dist/api/index.js +12 -12
  6. package/dist/api/lib/serverActions.d.ts +239 -239
  7. package/dist/api/lib/serverActions.d.ts.map +1 -1
  8. package/dist/api/lib/serverActions.js +834 -834
  9. package/dist/api/root.d.ts +828 -828
  10. package/dist/api/root.js +30 -30
  11. package/dist/api/routers/accountSettings.d.ts +60 -60
  12. package/dist/api/routers/accountSettings.js +108 -108
  13. package/dist/api/routers/admins.d.ts +105 -105
  14. package/dist/api/routers/admins.js +219 -219
  15. package/dist/api/routers/auth.d.ts +47 -47
  16. package/dist/api/routers/auth.js +25 -25
  17. package/dist/api/routers/categorySection.d.ts +103 -103
  18. package/dist/api/routers/categorySection.js +38 -38
  19. package/dist/api/routers/cmsSettings.d.ts +48 -48
  20. package/dist/api/routers/cmsSettings.js +51 -51
  21. package/dist/api/routers/cpanel.d.ts +83 -83
  22. package/dist/api/routers/cpanel.js +216 -216
  23. package/dist/api/routers/files.d.ts +47 -47
  24. package/dist/api/routers/files.js +23 -23
  25. package/dist/api/routers/gallery.d.ts +35 -35
  26. package/dist/api/routers/gallery.js +62 -62
  27. package/dist/api/routers/googleAnalytics.d.ts +30 -30
  28. package/dist/api/routers/googleAnalytics.js +7 -7
  29. package/dist/api/routers/hasItemsSection.d.ts +139 -139
  30. package/dist/api/routers/hasItemsSection.js +34 -34
  31. package/dist/api/routers/navigation.d.ts +51 -51
  32. package/dist/api/routers/navigation.js +11 -11
  33. package/dist/api/routers/simpleSection.d.ts +57 -57
  34. package/dist/api/routers/simpleSection.js +12 -12
  35. package/dist/api/trpc.d.ts +106 -106
  36. package/dist/api/trpc.js +72 -72
  37. package/dist/auth/axios/axiosInstance.d.ts +1 -1
  38. package/dist/auth/axios/axiosInstance.js +8 -8
  39. package/dist/auth/csrf.d.ts +29 -29
  40. package/dist/auth/csrf.js +76 -76
  41. package/dist/auth/hooks/index.d.ts +3 -3
  42. package/dist/auth/hooks/index.d.ts.map +1 -1
  43. package/dist/auth/hooks/index.js +3 -3
  44. package/dist/auth/hooks/useAxiosPrivate.d.ts +4 -4
  45. package/dist/auth/hooks/useAxiosPrivate.js +74 -74
  46. package/dist/auth/hooks/useRefreshToken.d.ts +6 -6
  47. package/dist/auth/hooks/useRefreshToken.js +79 -79
  48. package/dist/auth/index.d.ts +22 -22
  49. package/dist/auth/index.js +44 -44
  50. package/dist/auth/jwt.d.ts +5 -5
  51. package/dist/auth/jwt.js +25 -25
  52. package/dist/auth/lib/actions.d.ts +32 -32
  53. package/dist/auth/lib/actions.d.ts.map +1 -1
  54. package/dist/auth/lib/actions.js +209 -209
  55. package/dist/auth/lib/client.d.ts +3 -3
  56. package/dist/auth/lib/client.js +46 -46
  57. package/dist/auth/lib/index.d.ts +2 -2
  58. package/dist/auth/lib/index.d.ts.map +1 -1
  59. package/dist/auth/lib/index.js +2 -2
  60. package/dist/auth/react.d.ts +105 -105
  61. package/dist/auth/react.d.ts.map +1 -1
  62. package/dist/auth/react.js +347 -347
  63. package/dist/auth/trpc.d.ts +5 -5
  64. package/dist/auth/trpc.d.ts.map +1 -1
  65. package/dist/auth/trpc.js +81 -81
  66. package/dist/core/config/config-loader.d.ts +91 -91
  67. package/dist/core/config/config-loader.js +230 -230
  68. package/dist/core/config/index.d.ts +2 -2
  69. package/dist/core/config/index.d.ts.map +1 -1
  70. package/dist/core/config/index.js +1 -1
  71. package/dist/core/config/loader.d.ts +1 -1
  72. package/dist/core/config/loader.js +42 -42
  73. package/dist/core/db/index.d.ts +1 -1
  74. package/dist/core/db/index.d.ts.map +1 -1
  75. package/dist/core/db/index.js +1 -1
  76. package/dist/core/db/table-checker/DbTable.d.ts +5 -5
  77. package/dist/core/db/table-checker/DbTable.js +5 -5
  78. package/dist/core/db/table-checker/MysqlTable.d.ts +33 -33
  79. package/dist/core/db/table-checker/MysqlTable.d.ts.map +1 -1
  80. package/dist/core/db/table-checker/MysqlTable.js +94 -94
  81. package/dist/core/db/table-checker/index.d.ts +1 -1
  82. package/dist/core/db/table-checker/index.d.ts.map +1 -1
  83. package/dist/core/db/table-checker/index.js +1 -1
  84. package/dist/core/factories/FieldFactory.d.ts +123 -123
  85. package/dist/core/factories/FieldFactory.d.ts.map +1 -1
  86. package/dist/core/factories/FieldFactory.js +411 -411
  87. package/dist/core/factories/SectionFactory.d.ts +109 -109
  88. package/dist/core/factories/SectionFactory.d.ts.map +1 -1
  89. package/dist/core/factories/SectionFactory.js +415 -415
  90. package/dist/core/factories/index.d.ts +2 -2
  91. package/dist/core/factories/index.d.ts.map +1 -1
  92. package/dist/core/factories/index.js +2 -2
  93. package/dist/core/fields/checkbox.d.ts +62 -62
  94. package/dist/core/fields/checkbox.d.ts.map +1 -1
  95. package/dist/core/fields/checkbox.js +62 -62
  96. package/dist/core/fields/color.d.ts +83 -83
  97. package/dist/core/fields/color.d.ts.map +1 -1
  98. package/dist/core/fields/color.js +91 -91
  99. package/dist/core/fields/date.d.ts +99 -99
  100. package/dist/core/fields/date.d.ts.map +1 -1
  101. package/dist/core/fields/date.js +108 -108
  102. package/dist/core/fields/document.d.ts +179 -179
  103. package/dist/core/fields/document.d.ts.map +1 -1
  104. package/dist/core/fields/document.js +277 -277
  105. package/dist/core/fields/field-group.d.ts +17 -17
  106. package/dist/core/fields/field-group.d.ts.map +1 -1
  107. package/dist/core/fields/field-group.js +6 -6
  108. package/dist/core/fields/field.d.ts +125 -125
  109. package/dist/core/fields/field.d.ts.map +1 -1
  110. package/dist/core/fields/field.js +148 -148
  111. package/dist/core/fields/fileField.d.ts +14 -14
  112. package/dist/core/fields/fileField.d.ts.map +1 -1
  113. package/dist/core/fields/fileField.js +5 -5
  114. package/dist/core/fields/index.d.ts +64 -64
  115. package/dist/core/fields/index.d.ts.map +1 -1
  116. package/dist/core/fields/index.js +18 -18
  117. package/dist/core/fields/map.d.ts +166 -166
  118. package/dist/core/fields/map.d.ts.map +1 -1
  119. package/dist/core/fields/map.js +152 -152
  120. package/dist/core/fields/number.d.ts +185 -185
  121. package/dist/core/fields/number.d.ts.map +1 -1
  122. package/dist/core/fields/number.js +241 -241
  123. package/dist/core/fields/password.d.ts +108 -108
  124. package/dist/core/fields/password.d.ts.map +1 -1
  125. package/dist/core/fields/password.js +133 -133
  126. package/dist/core/fields/photo.d.ts +288 -288
  127. package/dist/core/fields/photo.d.ts.map +1 -1
  128. package/dist/core/fields/photo.js +410 -410
  129. package/dist/core/fields/richText.d.ts +294 -294
  130. package/dist/core/fields/richText.d.ts.map +1 -1
  131. package/dist/core/fields/richText.js +338 -338
  132. package/dist/core/fields/select.d.ts +365 -365
  133. package/dist/core/fields/select.d.ts.map +1 -1
  134. package/dist/core/fields/select.js +499 -499
  135. package/dist/core/fields/selectMultiple.d.ts +235 -235
  136. package/dist/core/fields/selectMultiple.d.ts.map +1 -1
  137. package/dist/core/fields/selectMultiple.js +417 -417
  138. package/dist/core/fields/tags.d.ts +130 -130
  139. package/dist/core/fields/tags.d.ts.map +1 -1
  140. package/dist/core/fields/tags.js +105 -105
  141. package/dist/core/fields/text.d.ts +135 -135
  142. package/dist/core/fields/text.d.ts.map +1 -1
  143. package/dist/core/fields/text.js +157 -157
  144. package/dist/core/fields/textArea.d.ts +106 -106
  145. package/dist/core/fields/textArea.d.ts.map +1 -1
  146. package/dist/core/fields/textArea.js +126 -126
  147. package/dist/core/fields/video.d.ts +147 -147
  148. package/dist/core/fields/video.d.ts.map +1 -1
  149. package/dist/core/fields/video.js +248 -248
  150. package/dist/core/helpers/entity.d.ts +7 -7
  151. package/dist/core/helpers/entity.js +27 -27
  152. package/dist/core/helpers/index.d.ts +4 -4
  153. package/dist/core/helpers/index.d.ts.map +1 -1
  154. package/dist/core/helpers/index.js +3 -3
  155. package/dist/core/index.d.ts +7 -7
  156. package/dist/core/index.d.ts.map +1 -1
  157. package/dist/core/index.js +7 -7
  158. package/dist/core/sections/category.d.ts +282 -282
  159. package/dist/core/sections/category.d.ts.map +1 -1
  160. package/dist/core/sections/category.js +147 -147
  161. package/dist/core/sections/hasItems.d.ts +631 -631
  162. package/dist/core/sections/hasItems.d.ts.map +1 -1
  163. package/dist/core/sections/hasItems.js +144 -144
  164. package/dist/core/sections/index.d.ts +4 -4
  165. package/dist/core/sections/index.d.ts.map +1 -1
  166. package/dist/core/sections/index.js +4 -4
  167. package/dist/core/sections/section.d.ts +225 -225
  168. package/dist/core/sections/section.d.ts.map +1 -1
  169. package/dist/core/sections/section.js +341 -341
  170. package/dist/core/sections/simple.d.ts +98 -98
  171. package/dist/core/sections/simple.d.ts.map +1 -1
  172. package/dist/core/sections/simple.js +95 -95
  173. package/dist/core/security/dom.d.ts +10 -10
  174. package/dist/core/security/dom.js +92 -92
  175. package/dist/core/submit/ItemEditSubmit.d.ts +75 -75
  176. package/dist/core/submit/ItemEditSubmit.js +186 -186
  177. package/dist/core/submit/NewItemSubmit.d.ts +13 -13
  178. package/dist/core/submit/NewItemSubmit.js +93 -93
  179. package/dist/core/submit/SimpleSectionSubmit.d.ts +12 -12
  180. package/dist/core/submit/SimpleSectionSubmit.js +93 -93
  181. package/dist/core/submit/index.d.ts +4 -4
  182. package/dist/core/submit/index.js +4 -4
  183. package/dist/core/submit/submit.d.ts +115 -115
  184. package/dist/core/submit/submit.js +479 -479
  185. package/dist/core/types/index.d.ts +279 -279
  186. package/dist/core/types/index.d.ts.map +1 -1
  187. package/dist/core/types/index.js +1 -1
  188. package/dist/db/client.d.ts +8 -8
  189. package/dist/db/client.d.ts.map +1 -1
  190. package/dist/db/client.js +19 -19
  191. package/dist/db/config.d.ts +5 -5
  192. package/dist/db/config.js +22 -22
  193. package/dist/db/drizzle.config.d.ts +5 -5
  194. package/dist/db/drizzle.config.js +18 -18
  195. package/dist/db/index.d.ts +2 -2
  196. package/dist/db/index.js +3 -3
  197. package/dist/db/schema.d.ts +638 -638
  198. package/dist/db/schema.js +73 -73
  199. package/dist/index.d.ts +7 -7
  200. package/dist/index.d.ts.map +1 -1
  201. package/dist/index.js +7 -7
  202. package/dist/translations/index.d.ts +2 -2
  203. package/dist/translations/index.js +15 -15
  204. package/dist/utils/CpanelApi.d.ts +24 -24
  205. package/dist/utils/CpanelApi.js +64 -64
  206. package/dist/utils/constants.d.ts +13 -13
  207. package/dist/utils/constants.js +61 -61
  208. package/dist/utils/index.d.ts +4 -4
  209. package/dist/utils/index.d.ts.map +1 -1
  210. package/dist/utils/index.js +4 -4
  211. package/dist/utils/utils.d.ts +59 -59
  212. package/dist/utils/utils.js +132 -132
  213. package/dist/validators/checkbox.d.ts +3 -3
  214. package/dist/validators/checkbox.d.ts.map +1 -1
  215. package/dist/validators/checkbox.js +12 -12
  216. package/dist/validators/color.d.ts +3 -3
  217. package/dist/validators/color.d.ts.map +1 -1
  218. package/dist/validators/color.js +7 -7
  219. package/dist/validators/date.d.ts +3 -3
  220. package/dist/validators/date.d.ts.map +1 -1
  221. package/dist/validators/date.js +5 -5
  222. package/dist/validators/document.d.ts +3 -3
  223. package/dist/validators/document.d.ts.map +1 -1
  224. package/dist/validators/document.js +57 -57
  225. package/dist/validators/index.d.ts +14 -14
  226. package/dist/validators/index.d.ts.map +1 -1
  227. package/dist/validators/index.js +14 -14
  228. package/dist/validators/map.d.ts +3 -3
  229. package/dist/validators/map.d.ts.map +1 -1
  230. package/dist/validators/map.js +5 -5
  231. package/dist/validators/number.d.ts +3 -3
  232. package/dist/validators/number.d.ts.map +1 -1
  233. package/dist/validators/number.js +20 -20
  234. package/dist/validators/password.d.ts +3 -3
  235. package/dist/validators/password.d.ts.map +1 -1
  236. package/dist/validators/password.js +11 -11
  237. package/dist/validators/photo.d.ts +3 -3
  238. package/dist/validators/photo.d.ts.map +1 -1
  239. package/dist/validators/photo.js +100 -100
  240. package/dist/validators/richText.d.ts +3 -3
  241. package/dist/validators/richText.d.ts.map +1 -1
  242. package/dist/validators/richText.js +8 -8
  243. package/dist/validators/select-multiple.d.ts +9 -9
  244. package/dist/validators/select-multiple.d.ts.map +1 -1
  245. package/dist/validators/select-multiple.js +20 -20
  246. package/dist/validators/select.d.ts +3 -3
  247. package/dist/validators/select.d.ts.map +1 -1
  248. package/dist/validators/select.js +5 -5
  249. package/dist/validators/text.d.ts +3 -3
  250. package/dist/validators/text.d.ts.map +1 -1
  251. package/dist/validators/text.js +7 -7
  252. package/dist/validators/textarea.d.ts +3 -3
  253. package/dist/validators/textarea.d.ts.map +1 -1
  254. package/dist/validators/textarea.js +7 -7
  255. package/dist/validators/video.d.ts +3 -3
  256. package/dist/validators/video.d.ts.map +1 -1
  257. package/dist/validators/video.js +57 -57
  258. package/package.json +4 -5
@@ -1,411 +1,411 @@
1
- import { sql } from 'drizzle-orm';
2
- import { db } from "../../db/client.js";
3
- import { SectionFactory } from "./SectionFactory.js";
4
- import { SimpleSection, HasItemsSection, CategorySection } from "../sections.js";
5
- import { checkboxField, SelectField, SelectMultipleField } from "../fields.js";
6
- import { is } from "../helpers.js";
7
- import { cloneDeep } from 'lodash-es';
8
- import chalk from 'chalk';
9
- export class FieldFactory {
10
- session;
11
- _values = {};
12
- itemId = undefined;
13
- _error = false;
14
- _errorMessage = '';
15
- type;
16
- sectionName;
17
- _sectionInfo;
18
- // private _fields: Field[]
19
- /**
20
- * Default order value for items without explicit order.
21
- * Ensures items without order come after explicitly ordered items.
22
- * Typical order values: 0-15, with occasional values up to 1000.
23
- */
24
- static DEFAULT_ORDER = 1_000_000;
25
- /**
26
- * Constructor
27
- */
28
- constructor({ type, sectionName, session, itemId }) {
29
- this.type = type;
30
- this.sectionName = sectionName;
31
- this.session = session;
32
- this.itemId = itemId;
33
- // this._fields = new Array<Field>()
34
- this._sectionInfo = undefined;
35
- }
36
- /**
37
- * Must be called after the constructor
38
- */
39
- async initialize() {
40
- await this.initializeSectionInfo(this.session);
41
- /**
42
- * If it's an edit operation, let's initialize the field values
43
- */
44
- if (this.type === 'edit') {
45
- await this.initializeFieldValues();
46
- }
47
- }
48
- async getFieldValue(field) {
49
- if (this.type === 'new')
50
- return undefined;
51
- try {
52
- return this._values[field.name];
53
- }
54
- catch (e) {
55
- return undefined;
56
- }
57
- }
58
- async initializeFieldValues() {
59
- if (this._error)
60
- return;
61
- if (this._sectionInfo?.db.table && this._sectionInfo.db.identifier) {
62
- /**
63
- * Let's get the section item values from its table and assign it to the `_values` property
64
- */
65
- // TODO: We still need to get the values from the `destinationDbTable` if it exists on any field
66
- // also, select_multiple fields need to be handled the same way, plus value is array of strings
67
- const [_v, fields] = await db.execute(sql `select * from ${sql.raw(this._sectionInfo.db.table)} where ${sql.raw(this._sectionInfo.db.identifier.name)} = ${this.itemId} limit 1`);
68
- // @ts-ignore
69
- // Bug: this is a bug in drizzle-orm/mysql2
70
- this._values = _v[0];
71
- /**
72
- * If the field has a destinationDb
73
- * Get the result from the field's table and the destinationDb's table using a JOIN query
74
- * Fields that can have a destinationDb are: SelectField, SelectMultipleField, TagsField
75
- */
76
- for (const field of this._sectionInfo.fields) {
77
- if (field.destinationDb && (is(field, SelectMultipleField) || is(field, SelectField))) {
78
- const sqlChunks = [
79
- sql `select * from ${sql.raw(field.destinationDb.table)} a JOIN ${sql.raw(field.db.table)} b ON a.${sql.raw(field.destinationDb.selectIdentifier)} = b.${sql.raw(field.db.identifier)} where ${sql.raw(field.destinationDb.itemIdentifier)} = ${this.itemId}`,
80
- ];
81
- if (is(field, SelectField) && field.hasDepth()) {
82
- /**
83
- * Add the where clause to the select statement
84
- */
85
- sqlChunks.push(sql ` ORDER BY b.level ASC`);
86
- }
87
- const [_rows, _fields] = await db.execute(sql.join(sqlChunks));
88
- const values = [];
89
- if (Array.isArray(_rows)) {
90
- for (const row of _rows) {
91
- values.push({
92
- value: row[field.destinationDb.selectIdentifier],
93
- label: row[field.db.label],
94
- });
95
- }
96
- }
97
- this._values[field.name] = values;
98
- }
99
- }
100
- /**
101
- * If the section item is not found, set the `_error` property to true
102
- */
103
- if (!is(this._sectionInfo, SimpleSection) && !this._values) {
104
- this._error = true;
105
- this._errorMessage = 'Item not found';
106
- return;
107
- }
108
- }
109
- else {
110
- this._error = true;
111
- this._errorMessage = 'Section table or section table identifier is missing';
112
- return;
113
- }
114
- }
115
- /**
116
- * Gets the section info and assigns it to the `sectionInfo` property
117
- * @param session
118
- * @private
119
- */
120
- async initializeSectionInfo(session) {
121
- /**
122
- * Get section info - could be a config (with build()) or an instance
123
- * Section files should export configs, but we handle both for backward compatibility
124
- */
125
- let _s = await this.getSectionInfo(session);
126
- if (_s === undefined)
127
- return;
128
- /**
129
- * Build the section from config when we need to use it
130
- * SectionFactory returns configs, so we always build them here
131
- *
132
- * Double check if the config has a build() method
133
- */
134
- if (typeof _s.build === 'function') {
135
- // It's a config - build it to get a fresh instance
136
- // We build here because we immediately need to access section properties (db.table, inputs, etc.)
137
- this._sectionInfo = _s.build();
138
- /**
139
- * Build the fields from the field configs
140
- */
141
- this._sectionInfo.buildFields();
142
- }
143
- else {
144
- const errorMessage = 'Section config must have a build() method. Use helper functions like simpleSection(), hasItemsSection(), categorySection() to create section configs.';
145
- console.error(chalk.bold.red('[FieldFactory Error]', errorMessage));
146
- this._error = true;
147
- this._errorMessage = errorMessage;
148
- throw new Error(errorMessage);
149
- }
150
- /**
151
- * Unset the `_s` variable to be garbage collected
152
- */
153
- _s = undefined;
154
- }
155
- /**
156
- * Get the section info from the `sectionsTable` table
157
- * @param session
158
- * @private
159
- */
160
- async getSectionInfo(session) {
161
- /**
162
- * First, let's get the section details, and then get the section input fields
163
- */
164
- const s = await SectionFactory.getSectionForAdmin({
165
- name: this.sectionName,
166
- admin: {
167
- id: session.user.id,
168
- requiredRole: this.type === 'new' ? 'C' : 'U',
169
- },
170
- });
171
- if (!s || !s.name) {
172
- this._error = true;
173
- this._errorMessage = 'Section not found or you do not have access to this operation';
174
- return undefined;
175
- }
176
- return s;
177
- }
178
- /**
179
- * Prepare a field for use
180
- * Fields are already instances (resolved in Section constructor), so we just need to set values
181
- * @param field
182
- */
183
- async prepareField(field) {
184
- // Fields are already instances - no need to build them
185
- // They were resolved from configs in the Section constructor
186
- if (this.type === 'edit') {
187
- /**
188
- * If it's an edit operation, get the value from the database and set it
189
- */
190
- const value = await this.getFieldValue(field);
191
- // It's important to set the value to whatever is in the database,
192
- // even if the field has a `defaultValue` set.
193
- field.setValue(value);
194
- }
195
- }
196
- // TODO: Implement this, maybe set the roles in the trpc context?
197
- addPermissionField() {
198
- if (is(this._sectionInfo, HasItemsSection)) {
199
- if (this._sectionInfo.requirePublishPermission && this.session.user) {
200
- return checkboxField({
201
- name: 'publish',
202
- label: 'Publish',
203
- required: true,
204
- order: 1000,
205
- });
206
- }
207
- }
208
- }
209
- /**
210
- * Generate the fields
211
- */
212
- async generateFields() {
213
- if (this._error)
214
- return;
215
- if (!this._sectionInfo)
216
- return;
217
- for (const input of this._sectionInfo.fields) {
218
- /**
219
- * If it's an edit operation, set the field to readonly if it's the identifier field,
220
- * identifier fields must not be edited!
221
- * They are always visible as a readonly text.
222
- */
223
- if (this.type === 'edit' && input.name === this._sectionInfo.db.identifier.name) {
224
- input.adminGenerated = 'readonly';
225
- }
226
- /**
227
- * Prepare the field (set values if editing)
228
- */
229
- await this.prepareField(input);
230
- }
231
- this.sortFields();
232
- }
233
- /**
234
- * Sort the fields.
235
- * This function sorts the conditional fields into their respective fields.
236
- */
237
- sortFields() {
238
- if (this._error)
239
- return;
240
- if (!this._sectionInfo)
241
- return;
242
- /**
243
- * Create a map of conditional fields
244
- */
245
- const _conditionalFieldMap = new Map();
246
- /**
247
- * Loop through the fields and get the conditional fields
248
- */
249
- for (const input of this._sectionInfo.fields) {
250
- input.conditionalFields = [];
251
- /**
252
- * Check if the field is conditional.
253
- */
254
- if (input.isConditional()) {
255
- for (const rule of input.conditionalRules ?? []) {
256
- /**
257
- * Add the conditional input to the conditional fields lists
258
- */
259
- if (!_conditionalFieldMap.has(rule.field.name)) {
260
- _conditionalFieldMap.set(rule.field.name, []);
261
- }
262
- _conditionalFieldMap.get(rule.field.name).push(
263
- /**
264
- * Clone the conditional field to avoid reference issues
265
- */
266
- cloneDeep({
267
- field: input.exportForClient(),
268
- rule: {
269
- condition: rule.condition,
270
- value: rule.value,
271
- },
272
- }));
273
- }
274
- }
275
- }
276
- /**
277
- * Assign conditionalFields to the appropriate fields
278
- */
279
- for (const field of this._sectionInfo.fields) {
280
- const matchingConditionalFields = _conditionalFieldMap.get(field.name);
281
- if (matchingConditionalFields) {
282
- field.conditionalFields.push(...matchingConditionalFields.map((clonedConditionalField) => ({
283
- ...clonedConditionalField,
284
- rule: {
285
- condition: clonedConditionalField.rule.condition,
286
- value: clonedConditionalField.rule.value,
287
- },
288
- })));
289
- }
290
- }
291
- /**
292
- * Destroy the map
293
- */
294
- _conditionalFieldMap.clear();
295
- }
296
- /**
297
- * Sort fields by their order property, preserving original position for fields without order.
298
- *
299
- * Sorting behavior:
300
- * 1. Fields with explicit `order` values are sorted by that value (ascending)
301
- * 2. Fields without `order` maintain their original array position (stable sort)
302
- * 3. Fields with the same `order` value maintain their relative positions (stable sort)
303
- *
304
- * This leverages JavaScript's stable Array.sort() (ES2019+) for optimal performance.
305
- * Note: This mutates the input array for performance.
306
- *
307
- * @param fields - Array of fields to sort (will be mutated)
308
- * @returns The sorted array (same reference as input)
309
- * @private
310
- */
311
- sortFieldsByOrder(fields) {
312
- // Sort in-place for performance (mutates array)
313
- // JavaScript's sort is stable (ES2019+), so equal values maintain original order
314
- return fields.sort((a, b) => (a.order ?? FieldFactory.DEFAULT_ORDER) - (b.order ?? FieldFactory.DEFAULT_ORDER));
315
- }
316
- /**
317
- * Sort groups by their groupOrder property, preserving original position for groups without order.
318
- *
319
- * Sorting behavior:
320
- * 1. Groups with explicit `groupOrder` values are sorted by that value (ascending)
321
- * 2. Groups without `groupOrder` maintain their original array position (stable sort)
322
- * 3. Groups with the same `groupOrder` value maintain their relative positions (stable sort)
323
- *
324
- * This leverages JavaScript's stable Array.sort() (ES2019+) for optimal performance.
325
- * Note: This mutates the input array for performance.
326
- * Note: groupOrder is normalized at creation time, so no nullish coalescing needed here.
327
- *
328
- * @param groups - Array of groups to sort (will be mutated)
329
- * @returns The sorted array (same reference as input)
330
- * @private
331
- */
332
- sortGroupsByOrder(groups) {
333
- // Sort in-place for performance (mutates array)
334
- // JavaScript's sort is stable (ES2019+), so equal values maintain original order
335
- // groupOrder is normalized at creation time, so direct subtraction is safe
336
- return groups.sort((a, b) => a.groupOrder - b.groupOrder);
337
- }
338
- /**
339
- * Get field groups with filtered and sorted fields.
340
- * Leverages the pre-built fieldGroups from the Section class for better performance.
341
- */
342
- getGroupedFields() {
343
- if (this._error)
344
- return;
345
- if (!this._sectionInfo)
346
- return;
347
- const fieldGroups = this._sectionInfo.fieldGroups;
348
- const isNewOperation = this.type === 'new';
349
- const isEditOperation = this.type === 'edit';
350
- const isCategorySection = is(this._sectionInfo, CategorySection);
351
- // Build groups in a single pass, skipping empty groups
352
- const processedGroups = [];
353
- for (let i = 0, len = fieldGroups.length; i < len; i++) {
354
- const group = fieldGroups[i];
355
- if (!group)
356
- continue;
357
- const fields = group.fields;
358
- const validFields = [];
359
- // Filter and collect valid fields in one pass
360
- for (let j = 0, fieldLen = fields.length; j < fieldLen; j++) {
361
- const field = fields[j];
362
- if (!field)
363
- continue;
364
- // Skip conditional fields (they're nested in their parent field)
365
- if (field.isConditional())
366
- continue;
367
- // For new item operations, exclude fields with adminGenerated not set to true
368
- if (isNewOperation && field.adminGenerated !== true)
369
- continue;
370
- // For edit operations, exclude fields with adminGenerated set to false
371
- if (isEditOperation && field.adminGenerated === false)
372
- continue;
373
- // For category sections, exclude parent_id and level fields
374
- if (isCategorySection && (field.name === 'parent_id' || field.name === 'level'))
375
- continue;
376
- /**
377
- * Add the field to the valid fields array
378
- */
379
- validFields.push(field);
380
- }
381
- /**
382
- * Only return the group if it has valid fields
383
- */
384
- if (validFields.length > 0) {
385
- processedGroups.push({
386
- groupId: group.id,
387
- groupTitle: group.title ?? '',
388
- // Normalize order value here to avoid checking again in sort function
389
- groupOrder: group.order ?? FieldFactory.DEFAULT_ORDER,
390
- inputs: this.sortFieldsByOrder(validFields).map((field) => field.exportForClient()),
391
- });
392
- }
393
- }
394
- // Sort groups by order using the optimized function
395
- this.sortGroupsByOrder(processedGroups);
396
- /**
397
- * Unset the section info to be garbage collected
398
- */
399
- this._sectionInfo = null;
400
- return processedGroups;
401
- }
402
- get sectionInfo() {
403
- return this._sectionInfo ?? undefined;
404
- }
405
- get errorMessage() {
406
- return this._errorMessage;
407
- }
408
- get error() {
409
- return this._error;
410
- }
411
- }
1
+ import { sql } from 'drizzle-orm';
2
+ import { db } from '../../db/client.js';
3
+ import { SectionFactory } from './SectionFactory.js';
4
+ import { SimpleSection, HasItemsSection, CategorySection } from '../sections/index.js';
5
+ import { checkboxField, SelectField, SelectMultipleField } from '../fields/index.js';
6
+ import { is } from '../helpers/index.js';
7
+ import { cloneDeep } from 'lodash-es';
8
+ import chalk from 'chalk';
9
+ export class FieldFactory {
10
+ session;
11
+ _values = {};
12
+ itemId = undefined;
13
+ _error = false;
14
+ _errorMessage = '';
15
+ type;
16
+ sectionName;
17
+ _sectionInfo;
18
+ // private _fields: Field[]
19
+ /**
20
+ * Default order value for items without explicit order.
21
+ * Ensures items without order come after explicitly ordered items.
22
+ * Typical order values: 0-15, with occasional values up to 1000.
23
+ */
24
+ static DEFAULT_ORDER = 1_000_000;
25
+ /**
26
+ * Constructor
27
+ */
28
+ constructor({ type, sectionName, session, itemId }) {
29
+ this.type = type;
30
+ this.sectionName = sectionName;
31
+ this.session = session;
32
+ this.itemId = itemId;
33
+ // this._fields = new Array<Field>()
34
+ this._sectionInfo = undefined;
35
+ }
36
+ /**
37
+ * Must be called after the constructor
38
+ */
39
+ async initialize() {
40
+ await this.initializeSectionInfo(this.session);
41
+ /**
42
+ * If it's an edit operation, let's initialize the field values
43
+ */
44
+ if (this.type === 'edit') {
45
+ await this.initializeFieldValues();
46
+ }
47
+ }
48
+ async getFieldValue(field) {
49
+ if (this.type === 'new')
50
+ return undefined;
51
+ try {
52
+ return this._values[field.name];
53
+ }
54
+ catch (e) {
55
+ return undefined;
56
+ }
57
+ }
58
+ async initializeFieldValues() {
59
+ if (this._error)
60
+ return;
61
+ if (this._sectionInfo?.db.table && this._sectionInfo.db.identifier) {
62
+ /**
63
+ * Let's get the section item values from its table and assign it to the `_values` property
64
+ */
65
+ // TODO: We still need to get the values from the `destinationDbTable` if it exists on any field
66
+ // also, select_multiple fields need to be handled the same way, plus value is array of strings
67
+ const [_v, fields] = await db.execute(sql `select * from ${sql.raw(this._sectionInfo.db.table)} where ${sql.raw(this._sectionInfo.db.identifier.name)} = ${this.itemId} limit 1`);
68
+ // @ts-ignore
69
+ // Bug: this is a bug in drizzle-orm/mysql2
70
+ this._values = _v[0];
71
+ /**
72
+ * If the field has a destinationDb
73
+ * Get the result from the field's table and the destinationDb's table using a JOIN query
74
+ * Fields that can have a destinationDb are: SelectField, SelectMultipleField, TagsField
75
+ */
76
+ for (const field of this._sectionInfo.fields) {
77
+ if (field.destinationDb && (is(field, SelectMultipleField) || is(field, SelectField))) {
78
+ const sqlChunks = [
79
+ sql `select * from ${sql.raw(field.destinationDb.table)} a JOIN ${sql.raw(field.db.table)} b ON a.${sql.raw(field.destinationDb.selectIdentifier)} = b.${sql.raw(field.db.identifier)} where ${sql.raw(field.destinationDb.itemIdentifier)} = ${this.itemId}`,
80
+ ];
81
+ if (is(field, SelectField) && field.hasDepth()) {
82
+ /**
83
+ * Add the where clause to the select statement
84
+ */
85
+ sqlChunks.push(sql ` ORDER BY b.level ASC`);
86
+ }
87
+ const [_rows, _fields] = await db.execute(sql.join(sqlChunks));
88
+ const values = [];
89
+ if (Array.isArray(_rows)) {
90
+ for (const row of _rows) {
91
+ values.push({
92
+ value: row[field.destinationDb.selectIdentifier],
93
+ label: row[field.db.label],
94
+ });
95
+ }
96
+ }
97
+ this._values[field.name] = values;
98
+ }
99
+ }
100
+ /**
101
+ * If the section item is not found, set the `_error` property to true
102
+ */
103
+ if (!is(this._sectionInfo, SimpleSection) && !this._values) {
104
+ this._error = true;
105
+ this._errorMessage = 'Item not found';
106
+ return;
107
+ }
108
+ }
109
+ else {
110
+ this._error = true;
111
+ this._errorMessage = 'Section table or section table identifier is missing';
112
+ return;
113
+ }
114
+ }
115
+ /**
116
+ * Gets the section info and assigns it to the `sectionInfo` property
117
+ * @param session
118
+ * @private
119
+ */
120
+ async initializeSectionInfo(session) {
121
+ /**
122
+ * Get section info - could be a config (with build()) or an instance
123
+ * Section files should export configs, but we handle both for backward compatibility
124
+ */
125
+ let _s = await this.getSectionInfo(session);
126
+ if (_s === undefined)
127
+ return;
128
+ /**
129
+ * Build the section from config when we need to use it
130
+ * SectionFactory returns configs, so we always build them here
131
+ *
132
+ * Double check if the config has a build() method
133
+ */
134
+ if (typeof _s.build === 'function') {
135
+ // It's a config - build it to get a fresh instance
136
+ // We build here because we immediately need to access section properties (db.table, inputs, etc.)
137
+ this._sectionInfo = _s.build();
138
+ /**
139
+ * Build the fields from the field configs
140
+ */
141
+ this._sectionInfo.buildFields();
142
+ }
143
+ else {
144
+ const errorMessage = 'Section config must have a build() method. Use helper functions like simpleSection(), hasItemsSection(), categorySection() to create section configs.';
145
+ console.error(chalk.bold.red('[FieldFactory Error]', errorMessage));
146
+ this._error = true;
147
+ this._errorMessage = errorMessage;
148
+ throw new Error(errorMessage);
149
+ }
150
+ /**
151
+ * Unset the `_s` variable to be garbage collected
152
+ */
153
+ _s = undefined;
154
+ }
155
+ /**
156
+ * Get the section info from the `sectionsTable` table
157
+ * @param session
158
+ * @private
159
+ */
160
+ async getSectionInfo(session) {
161
+ /**
162
+ * First, let's get the section details, and then get the section input fields
163
+ */
164
+ const s = await SectionFactory.getSectionForAdmin({
165
+ name: this.sectionName,
166
+ admin: {
167
+ id: session.user.id,
168
+ requiredRole: this.type === 'new' ? 'C' : 'U',
169
+ },
170
+ });
171
+ if (!s || !s.name) {
172
+ this._error = true;
173
+ this._errorMessage = 'Section not found or you do not have access to this operation';
174
+ return undefined;
175
+ }
176
+ return s;
177
+ }
178
+ /**
179
+ * Prepare a field for use
180
+ * Fields are already instances (resolved in Section constructor), so we just need to set values
181
+ * @param field
182
+ */
183
+ async prepareField(field) {
184
+ // Fields are already instances - no need to build them
185
+ // They were resolved from configs in the Section constructor
186
+ if (this.type === 'edit') {
187
+ /**
188
+ * If it's an edit operation, get the value from the database and set it
189
+ */
190
+ const value = await this.getFieldValue(field);
191
+ // It's important to set the value to whatever is in the database,
192
+ // even if the field has a `defaultValue` set.
193
+ field.setValue(value);
194
+ }
195
+ }
196
+ // TODO: Implement this, maybe set the roles in the trpc context?
197
+ addPermissionField() {
198
+ if (is(this._sectionInfo, HasItemsSection)) {
199
+ if (this._sectionInfo.requirePublishPermission && this.session.user) {
200
+ return checkboxField({
201
+ name: 'publish',
202
+ label: 'Publish',
203
+ required: true,
204
+ order: 1000,
205
+ });
206
+ }
207
+ }
208
+ }
209
+ /**
210
+ * Generate the fields
211
+ */
212
+ async generateFields() {
213
+ if (this._error)
214
+ return;
215
+ if (!this._sectionInfo)
216
+ return;
217
+ for (const input of this._sectionInfo.fields) {
218
+ /**
219
+ * If it's an edit operation, set the field to readonly if it's the identifier field,
220
+ * identifier fields must not be edited!
221
+ * They are always visible as a readonly text.
222
+ */
223
+ if (this.type === 'edit' && input.name === this._sectionInfo.db.identifier.name) {
224
+ input.adminGenerated = 'readonly';
225
+ }
226
+ /**
227
+ * Prepare the field (set values if editing)
228
+ */
229
+ await this.prepareField(input);
230
+ }
231
+ this.sortFields();
232
+ }
233
+ /**
234
+ * Sort the fields.
235
+ * This function sorts the conditional fields into their respective fields.
236
+ */
237
+ sortFields() {
238
+ if (this._error)
239
+ return;
240
+ if (!this._sectionInfo)
241
+ return;
242
+ /**
243
+ * Create a map of conditional fields
244
+ */
245
+ const _conditionalFieldMap = new Map();
246
+ /**
247
+ * Loop through the fields and get the conditional fields
248
+ */
249
+ for (const input of this._sectionInfo.fields) {
250
+ input.conditionalFields = [];
251
+ /**
252
+ * Check if the field is conditional.
253
+ */
254
+ if (input.isConditional()) {
255
+ for (const rule of input.conditionalRules ?? []) {
256
+ /**
257
+ * Add the conditional input to the conditional fields lists
258
+ */
259
+ if (!_conditionalFieldMap.has(rule.field.name)) {
260
+ _conditionalFieldMap.set(rule.field.name, []);
261
+ }
262
+ _conditionalFieldMap.get(rule.field.name).push(
263
+ /**
264
+ * Clone the conditional field to avoid reference issues
265
+ */
266
+ cloneDeep({
267
+ field: input.exportForClient(),
268
+ rule: {
269
+ condition: rule.condition,
270
+ value: rule.value,
271
+ },
272
+ }));
273
+ }
274
+ }
275
+ }
276
+ /**
277
+ * Assign conditionalFields to the appropriate fields
278
+ */
279
+ for (const field of this._sectionInfo.fields) {
280
+ const matchingConditionalFields = _conditionalFieldMap.get(field.name);
281
+ if (matchingConditionalFields) {
282
+ field.conditionalFields.push(...matchingConditionalFields.map((clonedConditionalField) => ({
283
+ ...clonedConditionalField,
284
+ rule: {
285
+ condition: clonedConditionalField.rule.condition,
286
+ value: clonedConditionalField.rule.value,
287
+ },
288
+ })));
289
+ }
290
+ }
291
+ /**
292
+ * Destroy the map
293
+ */
294
+ _conditionalFieldMap.clear();
295
+ }
296
+ /**
297
+ * Sort fields by their order property, preserving original position for fields without order.
298
+ *
299
+ * Sorting behavior:
300
+ * 1. Fields with explicit `order` values are sorted by that value (ascending)
301
+ * 2. Fields without `order` maintain their original array position (stable sort)
302
+ * 3. Fields with the same `order` value maintain their relative positions (stable sort)
303
+ *
304
+ * This leverages JavaScript's stable Array.sort() (ES2019+) for optimal performance.
305
+ * Note: This mutates the input array for performance.
306
+ *
307
+ * @param fields - Array of fields to sort (will be mutated)
308
+ * @returns The sorted array (same reference as input)
309
+ * @private
310
+ */
311
+ sortFieldsByOrder(fields) {
312
+ // Sort in-place for performance (mutates array)
313
+ // JavaScript's sort is stable (ES2019+), so equal values maintain original order
314
+ return fields.sort((a, b) => (a.order ?? FieldFactory.DEFAULT_ORDER) - (b.order ?? FieldFactory.DEFAULT_ORDER));
315
+ }
316
+ /**
317
+ * Sort groups by their groupOrder property, preserving original position for groups without order.
318
+ *
319
+ * Sorting behavior:
320
+ * 1. Groups with explicit `groupOrder` values are sorted by that value (ascending)
321
+ * 2. Groups without `groupOrder` maintain their original array position (stable sort)
322
+ * 3. Groups with the same `groupOrder` value maintain their relative positions (stable sort)
323
+ *
324
+ * This leverages JavaScript's stable Array.sort() (ES2019+) for optimal performance.
325
+ * Note: This mutates the input array for performance.
326
+ * Note: groupOrder is normalized at creation time, so no nullish coalescing needed here.
327
+ *
328
+ * @param groups - Array of groups to sort (will be mutated)
329
+ * @returns The sorted array (same reference as input)
330
+ * @private
331
+ */
332
+ sortGroupsByOrder(groups) {
333
+ // Sort in-place for performance (mutates array)
334
+ // JavaScript's sort is stable (ES2019+), so equal values maintain original order
335
+ // groupOrder is normalized at creation time, so direct subtraction is safe
336
+ return groups.sort((a, b) => a.groupOrder - b.groupOrder);
337
+ }
338
+ /**
339
+ * Get field groups with filtered and sorted fields.
340
+ * Leverages the pre-built fieldGroups from the Section class for better performance.
341
+ */
342
+ getGroupedFields() {
343
+ if (this._error)
344
+ return;
345
+ if (!this._sectionInfo)
346
+ return;
347
+ const fieldGroups = this._sectionInfo.fieldGroups;
348
+ const isNewOperation = this.type === 'new';
349
+ const isEditOperation = this.type === 'edit';
350
+ const isCategorySection = is(this._sectionInfo, CategorySection);
351
+ // Build groups in a single pass, skipping empty groups
352
+ const processedGroups = [];
353
+ for (let i = 0, len = fieldGroups.length; i < len; i++) {
354
+ const group = fieldGroups[i];
355
+ if (!group)
356
+ continue;
357
+ const fields = group.fields;
358
+ const validFields = [];
359
+ // Filter and collect valid fields in one pass
360
+ for (let j = 0, fieldLen = fields.length; j < fieldLen; j++) {
361
+ const field = fields[j];
362
+ if (!field)
363
+ continue;
364
+ // Skip conditional fields (they're nested in their parent field)
365
+ if (field.isConditional())
366
+ continue;
367
+ // For new item operations, exclude fields with adminGenerated not set to true
368
+ if (isNewOperation && field.adminGenerated !== true)
369
+ continue;
370
+ // For edit operations, exclude fields with adminGenerated set to false
371
+ if (isEditOperation && field.adminGenerated === false)
372
+ continue;
373
+ // For category sections, exclude parent_id and level fields
374
+ if (isCategorySection && (field.name === 'parent_id' || field.name === 'level'))
375
+ continue;
376
+ /**
377
+ * Add the field to the valid fields array
378
+ */
379
+ validFields.push(field);
380
+ }
381
+ /**
382
+ * Only return the group if it has valid fields
383
+ */
384
+ if (validFields.length > 0) {
385
+ processedGroups.push({
386
+ groupId: group.id,
387
+ groupTitle: group.title ?? '',
388
+ // Normalize order value here to avoid checking again in sort function
389
+ groupOrder: group.order ?? FieldFactory.DEFAULT_ORDER,
390
+ inputs: this.sortFieldsByOrder(validFields).map((field) => field.exportForClient()),
391
+ });
392
+ }
393
+ }
394
+ // Sort groups by order using the optimized function
395
+ this.sortGroupsByOrder(processedGroups);
396
+ /**
397
+ * Unset the section info to be garbage collected
398
+ */
399
+ this._sectionInfo = null;
400
+ return processedGroups;
401
+ }
402
+ get sectionInfo() {
403
+ return this._sectionInfo ?? undefined;
404
+ }
405
+ get errorMessage() {
406
+ return this._errorMessage;
407
+ }
408
+ get error() {
409
+ return this._error;
410
+ }
411
+ }