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.
- package/dist/api/axios/axiosInstance.d.ts +1 -1
- package/dist/api/axios/axiosInstance.js +8 -8
- package/dist/api/index.d.ts +855 -855
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +12 -12
- package/dist/api/lib/serverActions.d.ts +239 -239
- package/dist/api/lib/serverActions.d.ts.map +1 -1
- package/dist/api/lib/serverActions.js +834 -834
- package/dist/api/root.d.ts +828 -828
- package/dist/api/root.js +30 -30
- package/dist/api/routers/accountSettings.d.ts +60 -60
- package/dist/api/routers/accountSettings.js +108 -108
- package/dist/api/routers/admins.d.ts +105 -105
- package/dist/api/routers/admins.js +219 -219
- package/dist/api/routers/auth.d.ts +47 -47
- package/dist/api/routers/auth.js +25 -25
- package/dist/api/routers/categorySection.d.ts +103 -103
- package/dist/api/routers/categorySection.js +38 -38
- package/dist/api/routers/cmsSettings.d.ts +48 -48
- package/dist/api/routers/cmsSettings.js +51 -51
- package/dist/api/routers/cpanel.d.ts +83 -83
- package/dist/api/routers/cpanel.js +216 -216
- package/dist/api/routers/files.d.ts +47 -47
- package/dist/api/routers/files.js +23 -23
- package/dist/api/routers/gallery.d.ts +35 -35
- package/dist/api/routers/gallery.js +62 -62
- package/dist/api/routers/googleAnalytics.d.ts +30 -30
- package/dist/api/routers/googleAnalytics.js +7 -7
- package/dist/api/routers/hasItemsSection.d.ts +139 -139
- package/dist/api/routers/hasItemsSection.js +34 -34
- package/dist/api/routers/navigation.d.ts +51 -51
- package/dist/api/routers/navigation.js +11 -11
- package/dist/api/routers/simpleSection.d.ts +57 -57
- package/dist/api/routers/simpleSection.js +12 -12
- package/dist/api/trpc.d.ts +106 -106
- package/dist/api/trpc.js +72 -72
- package/dist/auth/axios/axiosInstance.d.ts +1 -1
- package/dist/auth/axios/axiosInstance.js +8 -8
- package/dist/auth/csrf.d.ts +29 -29
- package/dist/auth/csrf.js +76 -76
- package/dist/auth/hooks/index.d.ts +3 -3
- package/dist/auth/hooks/index.d.ts.map +1 -1
- package/dist/auth/hooks/index.js +3 -3
- package/dist/auth/hooks/useAxiosPrivate.d.ts +4 -4
- package/dist/auth/hooks/useAxiosPrivate.js +74 -74
- package/dist/auth/hooks/useRefreshToken.d.ts +6 -6
- package/dist/auth/hooks/useRefreshToken.js +79 -79
- package/dist/auth/index.d.ts +22 -22
- package/dist/auth/index.js +44 -44
- package/dist/auth/jwt.d.ts +5 -5
- package/dist/auth/jwt.js +25 -25
- package/dist/auth/lib/actions.d.ts +32 -32
- package/dist/auth/lib/actions.d.ts.map +1 -1
- package/dist/auth/lib/actions.js +209 -209
- package/dist/auth/lib/client.d.ts +3 -3
- package/dist/auth/lib/client.js +46 -46
- package/dist/auth/lib/index.d.ts +2 -2
- package/dist/auth/lib/index.d.ts.map +1 -1
- package/dist/auth/lib/index.js +2 -2
- package/dist/auth/react.d.ts +105 -105
- package/dist/auth/react.d.ts.map +1 -1
- package/dist/auth/react.js +347 -347
- package/dist/auth/trpc.d.ts +5 -5
- package/dist/auth/trpc.d.ts.map +1 -1
- package/dist/auth/trpc.js +81 -81
- package/dist/core/config/config-loader.d.ts +91 -91
- package/dist/core/config/config-loader.js +230 -230
- package/dist/core/config/index.d.ts +2 -2
- package/dist/core/config/index.d.ts.map +1 -1
- package/dist/core/config/index.js +1 -1
- package/dist/core/config/loader.d.ts +1 -1
- package/dist/core/config/loader.js +42 -42
- package/dist/core/db/index.d.ts +1 -1
- package/dist/core/db/index.d.ts.map +1 -1
- package/dist/core/db/index.js +1 -1
- package/dist/core/db/table-checker/DbTable.d.ts +5 -5
- package/dist/core/db/table-checker/DbTable.js +5 -5
- package/dist/core/db/table-checker/MysqlTable.d.ts +33 -33
- package/dist/core/db/table-checker/MysqlTable.d.ts.map +1 -1
- package/dist/core/db/table-checker/MysqlTable.js +94 -94
- package/dist/core/db/table-checker/index.d.ts +1 -1
- package/dist/core/db/table-checker/index.d.ts.map +1 -1
- package/dist/core/db/table-checker/index.js +1 -1
- package/dist/core/factories/FieldFactory.d.ts +123 -123
- package/dist/core/factories/FieldFactory.d.ts.map +1 -1
- package/dist/core/factories/FieldFactory.js +411 -411
- package/dist/core/factories/SectionFactory.d.ts +109 -109
- package/dist/core/factories/SectionFactory.d.ts.map +1 -1
- package/dist/core/factories/SectionFactory.js +415 -415
- package/dist/core/factories/index.d.ts +2 -2
- package/dist/core/factories/index.d.ts.map +1 -1
- package/dist/core/factories/index.js +2 -2
- package/dist/core/fields/checkbox.d.ts +62 -62
- package/dist/core/fields/checkbox.d.ts.map +1 -1
- package/dist/core/fields/checkbox.js +62 -62
- package/dist/core/fields/color.d.ts +83 -83
- package/dist/core/fields/color.d.ts.map +1 -1
- package/dist/core/fields/color.js +91 -91
- package/dist/core/fields/date.d.ts +99 -99
- package/dist/core/fields/date.d.ts.map +1 -1
- package/dist/core/fields/date.js +108 -108
- package/dist/core/fields/document.d.ts +179 -179
- package/dist/core/fields/document.d.ts.map +1 -1
- package/dist/core/fields/document.js +277 -277
- package/dist/core/fields/field-group.d.ts +17 -17
- package/dist/core/fields/field-group.d.ts.map +1 -1
- package/dist/core/fields/field-group.js +6 -6
- package/dist/core/fields/field.d.ts +125 -125
- package/dist/core/fields/field.d.ts.map +1 -1
- package/dist/core/fields/field.js +148 -148
- package/dist/core/fields/fileField.d.ts +14 -14
- package/dist/core/fields/fileField.d.ts.map +1 -1
- package/dist/core/fields/fileField.js +5 -5
- package/dist/core/fields/index.d.ts +64 -64
- package/dist/core/fields/index.d.ts.map +1 -1
- package/dist/core/fields/index.js +18 -18
- package/dist/core/fields/map.d.ts +166 -166
- package/dist/core/fields/map.d.ts.map +1 -1
- package/dist/core/fields/map.js +152 -152
- package/dist/core/fields/number.d.ts +185 -185
- package/dist/core/fields/number.d.ts.map +1 -1
- package/dist/core/fields/number.js +241 -241
- package/dist/core/fields/password.d.ts +108 -108
- package/dist/core/fields/password.d.ts.map +1 -1
- package/dist/core/fields/password.js +133 -133
- package/dist/core/fields/photo.d.ts +288 -288
- package/dist/core/fields/photo.d.ts.map +1 -1
- package/dist/core/fields/photo.js +410 -410
- package/dist/core/fields/richText.d.ts +294 -294
- package/dist/core/fields/richText.d.ts.map +1 -1
- package/dist/core/fields/richText.js +338 -338
- package/dist/core/fields/select.d.ts +365 -365
- package/dist/core/fields/select.d.ts.map +1 -1
- package/dist/core/fields/select.js +499 -499
- package/dist/core/fields/selectMultiple.d.ts +235 -235
- package/dist/core/fields/selectMultiple.d.ts.map +1 -1
- package/dist/core/fields/selectMultiple.js +417 -417
- package/dist/core/fields/tags.d.ts +130 -130
- package/dist/core/fields/tags.d.ts.map +1 -1
- package/dist/core/fields/tags.js +105 -105
- package/dist/core/fields/text.d.ts +135 -135
- package/dist/core/fields/text.d.ts.map +1 -1
- package/dist/core/fields/text.js +157 -157
- package/dist/core/fields/textArea.d.ts +106 -106
- package/dist/core/fields/textArea.d.ts.map +1 -1
- package/dist/core/fields/textArea.js +126 -126
- package/dist/core/fields/video.d.ts +147 -147
- package/dist/core/fields/video.d.ts.map +1 -1
- package/dist/core/fields/video.js +248 -248
- package/dist/core/helpers/entity.d.ts +7 -7
- package/dist/core/helpers/entity.js +27 -27
- package/dist/core/helpers/index.d.ts +4 -4
- package/dist/core/helpers/index.d.ts.map +1 -1
- package/dist/core/helpers/index.js +3 -3
- package/dist/core/index.d.ts +7 -7
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +7 -7
- package/dist/core/sections/category.d.ts +282 -282
- package/dist/core/sections/category.d.ts.map +1 -1
- package/dist/core/sections/category.js +147 -147
- package/dist/core/sections/hasItems.d.ts +631 -631
- package/dist/core/sections/hasItems.d.ts.map +1 -1
- package/dist/core/sections/hasItems.js +144 -144
- package/dist/core/sections/index.d.ts +4 -4
- package/dist/core/sections/index.d.ts.map +1 -1
- package/dist/core/sections/index.js +4 -4
- package/dist/core/sections/section.d.ts +225 -225
- package/dist/core/sections/section.d.ts.map +1 -1
- package/dist/core/sections/section.js +341 -341
- package/dist/core/sections/simple.d.ts +98 -98
- package/dist/core/sections/simple.d.ts.map +1 -1
- package/dist/core/sections/simple.js +95 -95
- package/dist/core/security/dom.d.ts +10 -10
- package/dist/core/security/dom.js +92 -92
- package/dist/core/submit/ItemEditSubmit.d.ts +75 -75
- package/dist/core/submit/ItemEditSubmit.js +186 -186
- package/dist/core/submit/NewItemSubmit.d.ts +13 -13
- package/dist/core/submit/NewItemSubmit.js +93 -93
- package/dist/core/submit/SimpleSectionSubmit.d.ts +12 -12
- package/dist/core/submit/SimpleSectionSubmit.js +93 -93
- package/dist/core/submit/index.d.ts +4 -4
- package/dist/core/submit/index.js +4 -4
- package/dist/core/submit/submit.d.ts +115 -115
- package/dist/core/submit/submit.js +479 -479
- package/dist/core/types/index.d.ts +279 -279
- package/dist/core/types/index.d.ts.map +1 -1
- package/dist/core/types/index.js +1 -1
- package/dist/db/client.d.ts +8 -8
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +19 -19
- package/dist/db/config.d.ts +5 -5
- package/dist/db/config.js +22 -22
- package/dist/db/drizzle.config.d.ts +5 -5
- package/dist/db/drizzle.config.js +18 -18
- package/dist/db/index.d.ts +2 -2
- package/dist/db/index.js +3 -3
- package/dist/db/schema.d.ts +638 -638
- package/dist/db/schema.js +73 -73
- package/dist/index.d.ts +7 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -7
- package/dist/translations/index.d.ts +2 -2
- package/dist/translations/index.js +15 -15
- package/dist/utils/CpanelApi.d.ts +24 -24
- package/dist/utils/CpanelApi.js +64 -64
- package/dist/utils/constants.d.ts +13 -13
- package/dist/utils/constants.js +61 -61
- package/dist/utils/index.d.ts +4 -4
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +4 -4
- package/dist/utils/utils.d.ts +59 -59
- package/dist/utils/utils.js +132 -132
- package/dist/validators/checkbox.d.ts +3 -3
- package/dist/validators/checkbox.d.ts.map +1 -1
- package/dist/validators/checkbox.js +12 -12
- package/dist/validators/color.d.ts +3 -3
- package/dist/validators/color.d.ts.map +1 -1
- package/dist/validators/color.js +7 -7
- package/dist/validators/date.d.ts +3 -3
- package/dist/validators/date.d.ts.map +1 -1
- package/dist/validators/date.js +5 -5
- package/dist/validators/document.d.ts +3 -3
- package/dist/validators/document.d.ts.map +1 -1
- package/dist/validators/document.js +57 -57
- package/dist/validators/index.d.ts +14 -14
- package/dist/validators/index.d.ts.map +1 -1
- package/dist/validators/index.js +14 -14
- package/dist/validators/map.d.ts +3 -3
- package/dist/validators/map.d.ts.map +1 -1
- package/dist/validators/map.js +5 -5
- package/dist/validators/number.d.ts +3 -3
- package/dist/validators/number.d.ts.map +1 -1
- package/dist/validators/number.js +20 -20
- package/dist/validators/password.d.ts +3 -3
- package/dist/validators/password.d.ts.map +1 -1
- package/dist/validators/password.js +11 -11
- package/dist/validators/photo.d.ts +3 -3
- package/dist/validators/photo.d.ts.map +1 -1
- package/dist/validators/photo.js +100 -100
- package/dist/validators/richText.d.ts +3 -3
- package/dist/validators/richText.d.ts.map +1 -1
- package/dist/validators/richText.js +8 -8
- package/dist/validators/select-multiple.d.ts +9 -9
- package/dist/validators/select-multiple.d.ts.map +1 -1
- package/dist/validators/select-multiple.js +20 -20
- package/dist/validators/select.d.ts +3 -3
- package/dist/validators/select.d.ts.map +1 -1
- package/dist/validators/select.js +5 -5
- package/dist/validators/text.d.ts +3 -3
- package/dist/validators/text.d.ts.map +1 -1
- package/dist/validators/text.js +7 -7
- package/dist/validators/textarea.d.ts +3 -3
- package/dist/validators/textarea.d.ts.map +1 -1
- package/dist/validators/textarea.js +7 -7
- package/dist/validators/video.d.ts +3 -3
- package/dist/validators/video.d.ts.map +1 -1
- package/dist/validators/video.js +57 -57
- package/package.json +4 -5
|
@@ -1,417 +1,417 @@
|
|
|
1
|
-
import { Field, baseFieldConfigSchema } from
|
|
2
|
-
import { entityKind } from
|
|
3
|
-
import { selectFieldDestinationDbSchema, selectOptionSchema } from
|
|
4
|
-
import { MysqlTableChecker } from
|
|
5
|
-
import { db } from
|
|
6
|
-
import { sql } from 'drizzle-orm';
|
|
7
|
-
import chalk from 'chalk';
|
|
8
|
-
import * as z from 'zod';
|
|
9
|
-
/*export type SelectMultipleValue = {
|
|
10
|
-
value: string | number
|
|
11
|
-
label: string
|
|
12
|
-
}*/
|
|
13
|
-
const selectMultipleSharedExtrasSchema = z.strictObject({
|
|
14
|
-
/**
|
|
15
|
-
* A destination table where the select values will be saved
|
|
16
|
-
* When this is set, the value of the field will only be saved in the destination table as one row per each value
|
|
17
|
-
*/
|
|
18
|
-
destinationDb: selectFieldDestinationDbSchema.optional(),
|
|
19
|
-
checkValuesExist: z.boolean().optional().describe('Validate submitted values exist in the options'),
|
|
20
|
-
});
|
|
21
|
-
const selectMultipleFieldDbConfigSchema = selectMultipleSharedExtrasSchema.extend({
|
|
22
|
-
options: z.never().optional(),
|
|
23
|
-
db: z
|
|
24
|
-
.strictObject({
|
|
25
|
-
table: z.string().min(1).describe('Database table to read select options from'),
|
|
26
|
-
identifier: z.string().min(1).describe('Identifier column for the option'),
|
|
27
|
-
label: z.string().min(1).describe('Label column for the option'),
|
|
28
|
-
orderBy: z.string().optional().describe('Optional order by column'),
|
|
29
|
-
})
|
|
30
|
-
.describe('Database configuration for select options'),
|
|
31
|
-
section: z.never().optional(),
|
|
32
|
-
});
|
|
33
|
-
const selectMultipleFieldSectionConfigSchema = selectMultipleSharedExtrasSchema.extend({
|
|
34
|
-
options: z.array(selectOptionSchema).optional(),
|
|
35
|
-
db: z.never().optional(),
|
|
36
|
-
section: z
|
|
37
|
-
.custom()
|
|
38
|
-
.describe('Section to derive select options from'),
|
|
39
|
-
});
|
|
40
|
-
const selectMultipleFieldStaticConfigSchema = selectMultipleSharedExtrasSchema.extend({
|
|
41
|
-
options: z.array(selectOptionSchema).min(1).describe('List of static select options'),
|
|
42
|
-
db: z.never().optional(),
|
|
43
|
-
section: z.never().optional(),
|
|
44
|
-
});
|
|
45
|
-
const selectMultipleFieldConfigSchema = z.union([
|
|
46
|
-
selectMultipleFieldDbConfigSchema,
|
|
47
|
-
selectMultipleFieldSectionConfigSchema,
|
|
48
|
-
selectMultipleFieldStaticConfigSchema,
|
|
49
|
-
]);
|
|
50
|
-
export class SelectMultipleField extends Field {
|
|
51
|
-
static [entityKind] = 'SelectMultipleField';
|
|
52
|
-
checkValuesExist;
|
|
53
|
-
optionsType;
|
|
54
|
-
db;
|
|
55
|
-
/*protected*/ options;
|
|
56
|
-
destinationDb;
|
|
57
|
-
value = undefined;
|
|
58
|
-
_itemIdentifier;
|
|
59
|
-
constructor(config) {
|
|
60
|
-
super(config, 'select_multiple');
|
|
61
|
-
this.checkValuesExist = config.checkValuesExist ?? true;
|
|
62
|
-
this.destinationDb = config.destinationDb;
|
|
63
|
-
/**
|
|
64
|
-
* If options are static, set them
|
|
65
|
-
*/
|
|
66
|
-
if (config.options && config.options?.length > 0) {
|
|
67
|
-
this.options = config.options;
|
|
68
|
-
this.optionsType = 'static';
|
|
69
|
-
this.db = null;
|
|
70
|
-
}
|
|
71
|
-
else if (config.db) {
|
|
72
|
-
/**
|
|
73
|
-
* Else, check if the db config is set
|
|
74
|
-
*/
|
|
75
|
-
this.db = config.db;
|
|
76
|
-
this.optionsType = 'db';
|
|
77
|
-
}
|
|
78
|
-
else if (config.section) {
|
|
79
|
-
/**
|
|
80
|
-
* Else, check if the section config is set
|
|
81
|
-
*/
|
|
82
|
-
/**
|
|
83
|
-
* Runtime validation: Ensure the section is a HasItemsSection or CategorySection
|
|
84
|
-
* TypeScript ensures type safety at compile time, but runtime data (from JSON/APIs)
|
|
85
|
-
* can violate types, so we validate here for safety.
|
|
86
|
-
*/
|
|
87
|
-
const sectionType = config.section.type;
|
|
88
|
-
if (sectionType !== 'has_items' && sectionType !== 'category') {
|
|
89
|
-
const message = `[SelectMultipleField: ${this.label}]: Section must be a HasItemsSection or CategorySection, but got type: ${sectionType ?? 'undefined'}`;
|
|
90
|
-
console.error(chalk.red.bold(message));
|
|
91
|
-
console.error(chalk.yellow('Please make sure the section is a HasItemsSection or CategorySection'));
|
|
92
|
-
throw new Error(message);
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* Build the section and set the options type to section
|
|
96
|
-
*/
|
|
97
|
-
let section = config.section.build();
|
|
98
|
-
this.optionsType = 'section';
|
|
99
|
-
this.db = {
|
|
100
|
-
table: section.db.table,
|
|
101
|
-
identifier: section.db.identifier.name,
|
|
102
|
-
label: section.headingField.name,
|
|
103
|
-
orderBy: section.db.orderByField ? section.db.orderByField.name : section.db.identifier.name,
|
|
104
|
-
};
|
|
105
|
-
/**
|
|
106
|
-
* Set the section instance to undefined to avoid memory leaks
|
|
107
|
-
*/
|
|
108
|
-
section = undefined;
|
|
109
|
-
}
|
|
110
|
-
else {
|
|
111
|
-
throw new Error('Select field requires either db, section or an options array.');
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
static getEntityKind() {
|
|
115
|
-
return SelectMultipleField[entityKind];
|
|
116
|
-
}
|
|
117
|
-
hasSqlNameAndValue() {
|
|
118
|
-
return this.destinationDb === undefined;
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* Get the value of the field
|
|
122
|
-
*/
|
|
123
|
-
getValue() {
|
|
124
|
-
return this.value;
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* Get the value of the field when submitting to the database
|
|
128
|
-
*/
|
|
129
|
-
getSubmitValue() {
|
|
130
|
-
if (!this.hasSqlNameAndValue())
|
|
131
|
-
return;
|
|
132
|
-
/**
|
|
133
|
-
* Set the value as a comma separated string of the values
|
|
134
|
-
*/
|
|
135
|
-
return this.value?.map((value) => value.value).join(',');
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Set the value of the field
|
|
139
|
-
* @param value can be a string of comma-separated values coming from the db table,
|
|
140
|
-
* or an array of `SelectOption` objects coming from the client
|
|
141
|
-
*/
|
|
142
|
-
setValue(value) {
|
|
143
|
-
if (typeof value === 'string') {
|
|
144
|
-
/**
|
|
145
|
-
* Value can be an encoded JSON string sent from the client,
|
|
146
|
-
* try parsing the value as a JSON string
|
|
147
|
-
*/
|
|
148
|
-
let decoded;
|
|
149
|
-
try {
|
|
150
|
-
decoded = JSON.parse(value);
|
|
151
|
-
}
|
|
152
|
-
catch (error) {
|
|
153
|
-
decoded = undefined;
|
|
154
|
-
}
|
|
155
|
-
if (!Array.isArray(decoded)) {
|
|
156
|
-
/**
|
|
157
|
-
* If the decoded value is not an array (which means it should be a string coming from the db table),
|
|
158
|
-
* split the values by comma and set the value as an array of objects
|
|
159
|
-
*/
|
|
160
|
-
const values = value.split(',');
|
|
161
|
-
values.map((value) => {
|
|
162
|
-
/**
|
|
163
|
-
* Check if the value exists in the options and return the value and text
|
|
164
|
-
*/
|
|
165
|
-
const option = this.options?.find((option) => option.value.toString() === value.toString());
|
|
166
|
-
if (option) {
|
|
167
|
-
if (!this.value) {
|
|
168
|
-
this.value = [];
|
|
169
|
-
}
|
|
170
|
-
this.value.push(option);
|
|
171
|
-
}
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
else {
|
|
175
|
-
/**
|
|
176
|
-
* If the decoded value is an array, set the value as the decoded value
|
|
177
|
-
*/
|
|
178
|
-
this.value = decoded;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
else {
|
|
182
|
-
this.value = value;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
exportForClient() {
|
|
186
|
-
return {
|
|
187
|
-
...super.exportForClient(),
|
|
188
|
-
options: this.options,
|
|
189
|
-
value: this.value,
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
async postSubmit(itemId) {
|
|
193
|
-
if (!this.destinationDb)
|
|
194
|
-
return;
|
|
195
|
-
this._itemIdentifier = itemId;
|
|
196
|
-
try {
|
|
197
|
-
/**
|
|
198
|
-
* Delete the existing values
|
|
199
|
-
*/
|
|
200
|
-
await db.execute(sql `DELETE FROM \`${sql.raw(this.destinationDb.table)}\` WHERE \`${sql.raw(this.destinationDb.itemIdentifier)}\` = ${this._itemIdentifier}`);
|
|
201
|
-
/**
|
|
202
|
-
* Prepare the sql statement
|
|
203
|
-
*/
|
|
204
|
-
const sqlChunks = [];
|
|
205
|
-
sqlChunks.push(sql `INSERT INTO \`${sql.raw(this.destinationDb.table)}\` (\`${sql.raw(this.destinationDb.selectIdentifier)}\`, \`${sql.raw(this.destinationDb.itemIdentifier)}\`) VALUES `);
|
|
206
|
-
/**
|
|
207
|
-
* Loop through the values and add them to the sql statement values
|
|
208
|
-
*/
|
|
209
|
-
if (this.value && this.value.length > 0) {
|
|
210
|
-
const count = this.value.length;
|
|
211
|
-
this.value.forEach((value, index) => {
|
|
212
|
-
sqlChunks.push(sql `(${value.value}, ${this._itemIdentifier})`);
|
|
213
|
-
if (index < count - 1) {
|
|
214
|
-
/**
|
|
215
|
-
* Add a comma to separate the values in the sql statement
|
|
216
|
-
*/
|
|
217
|
-
sqlChunks.push(sql `, `);
|
|
218
|
-
}
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
/**
|
|
222
|
-
* Join the sql chunks
|
|
223
|
-
*/
|
|
224
|
-
const finalSql = sql.join(sqlChunks);
|
|
225
|
-
/**
|
|
226
|
-
* Execute the sql statement
|
|
227
|
-
*/
|
|
228
|
-
await db.execute(finalSql);
|
|
229
|
-
}
|
|
230
|
-
catch (error) {
|
|
231
|
-
throw new Error(`${this.label}: Error saving values to the destination table`);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
async postSubmitRollback() {
|
|
235
|
-
if (!this.destinationDb)
|
|
236
|
-
return;
|
|
237
|
-
if (!this._itemIdentifier) {
|
|
238
|
-
throw new Error(`${this.label}: Item identifier is not set. Make sure to set the item identifier by calling postSubmit() before calling postSubmitRollback()`);
|
|
239
|
-
}
|
|
240
|
-
try {
|
|
241
|
-
await db.execute(sql `DELETE FROM \`${sql.raw(this.destinationDb.table)}\` WHERE \`${sql.raw(this.destinationDb.itemIdentifier)}\` = ${this._itemIdentifier}`);
|
|
242
|
-
}
|
|
243
|
-
catch (error) {
|
|
244
|
-
throw new Error(`${this.label}: Error deleting values from the destination table`);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
/**
|
|
248
|
-
* Build the field to be used in a form
|
|
249
|
-
*/
|
|
250
|
-
async build() {
|
|
251
|
-
if (this.destinationDb) {
|
|
252
|
-
this.checkDestinationDBConfig();
|
|
253
|
-
await this.checkDestinationDBTableAndColumns();
|
|
254
|
-
}
|
|
255
|
-
/**
|
|
256
|
-
* If options are already set, return
|
|
257
|
-
*/
|
|
258
|
-
if (this.options && this.optionsType === 'static')
|
|
259
|
-
return;
|
|
260
|
-
/**
|
|
261
|
-
* Initialize the select db and options
|
|
262
|
-
*/
|
|
263
|
-
this.checkDBConfig();
|
|
264
|
-
await this.checkDBTableAndColumns();
|
|
265
|
-
/**
|
|
266
|
-
* Fetch the options
|
|
267
|
-
*/
|
|
268
|
-
await this.fetchOptions();
|
|
269
|
-
}
|
|
270
|
-
async fetchOptions() {
|
|
271
|
-
/**
|
|
272
|
-
* Let's get the options for the select input
|
|
273
|
-
*/
|
|
274
|
-
let selectStatement = `select * from \`${this.db.table}\` ORDER BY \`${this.db.orderBy ?? this.db.identifier}\` DESC`;
|
|
275
|
-
/**
|
|
276
|
-
* Get the options from the table
|
|
277
|
-
*/
|
|
278
|
-
const selectOptionsRows = await db.execute(sql.raw(selectStatement));
|
|
279
|
-
const rows = selectOptionsRows[0];
|
|
280
|
-
const inputSelectTableIdentifierField = this.db.identifier;
|
|
281
|
-
const inputSelectTableHeadingField = this.db.label;
|
|
282
|
-
/**
|
|
283
|
-
* Set the options
|
|
284
|
-
*/
|
|
285
|
-
this.options = rows.map((row) => ({
|
|
286
|
-
value: row[inputSelectTableIdentifierField],
|
|
287
|
-
label: row[inputSelectTableHeadingField],
|
|
288
|
-
}));
|
|
289
|
-
}
|
|
290
|
-
/**
|
|
291
|
-
* Check if table and columns exist
|
|
292
|
-
* @private
|
|
293
|
-
*/
|
|
294
|
-
async checkDBTableAndColumns() {
|
|
295
|
-
const columns = await MysqlTableChecker.getColumns(this.db.table);
|
|
296
|
-
if (!columns.includes(this.db.identifier) || !columns.includes(this.db.label)) {
|
|
297
|
-
throw new Error(`Field ${this.label}: Table ${this.db.table} and/or columns: ${this.db.identifier}, ${this.db.label} do not exist in the database!`);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
/**
|
|
301
|
-
* Check if destination table and columns exist
|
|
302
|
-
* @private
|
|
303
|
-
*/
|
|
304
|
-
async checkDestinationDBTableAndColumns() {
|
|
305
|
-
if (!this.destinationDb) {
|
|
306
|
-
throw new Error(`Field ${this.label}: Destination table, select identifier and item identifier are required`);
|
|
307
|
-
}
|
|
308
|
-
const columns = await MysqlTableChecker.getColumns(this.destinationDb.table);
|
|
309
|
-
if (!columns.includes(this.destinationDb.selectIdentifier) ||
|
|
310
|
-
!columns.includes(this.destinationDb.itemIdentifier)) {
|
|
311
|
-
throw new Error(`Field ${this.label}: Table ${this.destinationDb.table} and/or columns: ${this.destinationDb.selectIdentifier}, ${this.destinationDb.itemIdentifier} do not exist in the database!`);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
/**
|
|
315
|
-
* Check the db config is set
|
|
316
|
-
* @private
|
|
317
|
-
*/
|
|
318
|
-
checkDBConfig() {
|
|
319
|
-
if (this.db.table.trim().length === 0 ||
|
|
320
|
-
this.db.identifier.trim().length === 0 ||
|
|
321
|
-
this.db.label.trim().length === 0) {
|
|
322
|
-
throw new Error(`Field ${this.label}: Select table, identifier and label are required`);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
/**
|
|
326
|
-
* Check the destination db config is set
|
|
327
|
-
* @private
|
|
328
|
-
*/
|
|
329
|
-
checkDestinationDBConfig() {
|
|
330
|
-
if (this.destinationDb?.table.trim().length === 0 ||
|
|
331
|
-
this.destinationDb?.selectIdentifier.trim().length === 0 ||
|
|
332
|
-
this.destinationDb?.itemIdentifier.trim().length === 0) {
|
|
333
|
-
throw new Error(`Field ${this.label}: Destination table, select identifier and item identifier are required`);
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
checkRequired() {
|
|
337
|
-
/**
|
|
338
|
-
* Check if the field is required
|
|
339
|
-
*/
|
|
340
|
-
if (this.required) {
|
|
341
|
-
if (!this.value || this.value.length === 0) {
|
|
342
|
-
throw new Error(`Field ${this.label} is required`);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
/**
|
|
347
|
-
* Prepare the field for submission
|
|
348
|
-
*/
|
|
349
|
-
async prepareForSubmission() {
|
|
350
|
-
if (this.optionsType !== 'static') {
|
|
351
|
-
this.checkDBConfig();
|
|
352
|
-
await this.checkDBTableAndColumns();
|
|
353
|
-
/**
|
|
354
|
-
* Fetch the options
|
|
355
|
-
*/
|
|
356
|
-
await this.fetchOptions();
|
|
357
|
-
}
|
|
358
|
-
if (this.destinationDb) {
|
|
359
|
-
this.checkDestinationDBConfig();
|
|
360
|
-
await this.checkDestinationDBTableAndColumns();
|
|
361
|
-
}
|
|
362
|
-
if (this.checkValuesExist && this.value && this.value.length > 0) {
|
|
363
|
-
/**
|
|
364
|
-
* Check if the values exist in the table
|
|
365
|
-
* This is now safe because we have verified the table and columns exist in the database
|
|
366
|
-
*/
|
|
367
|
-
this.value.forEach((value) => {
|
|
368
|
-
if (!this.options?.find((option) => option.value.toString() === value.value.toString())) {
|
|
369
|
-
throw new Error(`Field ${this.label}: Option ${value.label} does not exist in the options`);
|
|
370
|
-
}
|
|
371
|
-
});
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
const selectMultipleFieldDbOptionsSchema = z.strictObject({
|
|
376
|
-
...baseFieldConfigSchema.shape,
|
|
377
|
-
...selectMultipleFieldDbConfigSchema.shape,
|
|
378
|
-
});
|
|
379
|
-
const selectMultipleFieldSectionOptionsSchema = z.strictObject({
|
|
380
|
-
...baseFieldConfigSchema.shape,
|
|
381
|
-
...selectMultipleFieldSectionConfigSchema.shape,
|
|
382
|
-
});
|
|
383
|
-
const selectMultipleFieldStaticOptionsSchema = z.strictObject({
|
|
384
|
-
...baseFieldConfigSchema.shape,
|
|
385
|
-
...selectMultipleFieldStaticConfigSchema.shape,
|
|
386
|
-
});
|
|
387
|
-
const selectMultipleFieldOptionsSchema = z.union([
|
|
388
|
-
selectMultipleFieldDbOptionsSchema,
|
|
389
|
-
selectMultipleFieldSectionOptionsSchema,
|
|
390
|
-
selectMultipleFieldStaticOptionsSchema,
|
|
391
|
-
]);
|
|
392
|
-
const selectMultipleFieldHelperConfigSchema = z.intersection(selectMultipleFieldOptionsSchema, z.strictObject({
|
|
393
|
-
type: z.literal('select_multiple').describe('The type of the field'),
|
|
394
|
-
build: z
|
|
395
|
-
.function()
|
|
396
|
-
.output(z.instanceof(SelectMultipleField))
|
|
397
|
-
.describe('Build a SelectMultipleField instance from this config'),
|
|
398
|
-
}));
|
|
399
|
-
/**
|
|
400
|
-
* Helper function to create a multiple select field configuration
|
|
401
|
-
* Returns a config object with a build() method that can be serialized and used anywhere.
|
|
402
|
-
*/
|
|
403
|
-
export function selectMultipleField(field) {
|
|
404
|
-
const result = selectMultipleFieldOptionsSchema.safeParse(field);
|
|
405
|
-
if (!result.success) {
|
|
406
|
-
throw new Error(`[Field: ${field.name}]: ${z.prettifyError(result.error)}`);
|
|
407
|
-
}
|
|
408
|
-
const parsedField = result.data;
|
|
409
|
-
const config = {
|
|
410
|
-
...parsedField,
|
|
411
|
-
type: 'select_multiple',
|
|
412
|
-
build() {
|
|
413
|
-
return new SelectMultipleField(parsedField);
|
|
414
|
-
},
|
|
415
|
-
};
|
|
416
|
-
return config;
|
|
417
|
-
}
|
|
1
|
+
import { Field, baseFieldConfigSchema } from './field.js';
|
|
2
|
+
import { entityKind } from '../helpers/index.js';
|
|
3
|
+
import { selectFieldDestinationDbSchema, selectOptionSchema } from './select.js';
|
|
4
|
+
import { MysqlTableChecker } from '../db/index.js';
|
|
5
|
+
import { db } from '../../db/client.js';
|
|
6
|
+
import { sql } from 'drizzle-orm';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import * as z from 'zod';
|
|
9
|
+
/*export type SelectMultipleValue = {
|
|
10
|
+
value: string | number
|
|
11
|
+
label: string
|
|
12
|
+
}*/
|
|
13
|
+
const selectMultipleSharedExtrasSchema = z.strictObject({
|
|
14
|
+
/**
|
|
15
|
+
* A destination table where the select values will be saved
|
|
16
|
+
* When this is set, the value of the field will only be saved in the destination table as one row per each value
|
|
17
|
+
*/
|
|
18
|
+
destinationDb: selectFieldDestinationDbSchema.optional(),
|
|
19
|
+
checkValuesExist: z.boolean().optional().describe('Validate submitted values exist in the options'),
|
|
20
|
+
});
|
|
21
|
+
const selectMultipleFieldDbConfigSchema = selectMultipleSharedExtrasSchema.extend({
|
|
22
|
+
options: z.never().optional(),
|
|
23
|
+
db: z
|
|
24
|
+
.strictObject({
|
|
25
|
+
table: z.string().min(1).describe('Database table to read select options from'),
|
|
26
|
+
identifier: z.string().min(1).describe('Identifier column for the option'),
|
|
27
|
+
label: z.string().min(1).describe('Label column for the option'),
|
|
28
|
+
orderBy: z.string().optional().describe('Optional order by column'),
|
|
29
|
+
})
|
|
30
|
+
.describe('Database configuration for select options'),
|
|
31
|
+
section: z.never().optional(),
|
|
32
|
+
});
|
|
33
|
+
const selectMultipleFieldSectionConfigSchema = selectMultipleSharedExtrasSchema.extend({
|
|
34
|
+
options: z.array(selectOptionSchema).optional(),
|
|
35
|
+
db: z.never().optional(),
|
|
36
|
+
section: z
|
|
37
|
+
.custom()
|
|
38
|
+
.describe('Section to derive select options from'),
|
|
39
|
+
});
|
|
40
|
+
const selectMultipleFieldStaticConfigSchema = selectMultipleSharedExtrasSchema.extend({
|
|
41
|
+
options: z.array(selectOptionSchema).min(1).describe('List of static select options'),
|
|
42
|
+
db: z.never().optional(),
|
|
43
|
+
section: z.never().optional(),
|
|
44
|
+
});
|
|
45
|
+
const selectMultipleFieldConfigSchema = z.union([
|
|
46
|
+
selectMultipleFieldDbConfigSchema,
|
|
47
|
+
selectMultipleFieldSectionConfigSchema,
|
|
48
|
+
selectMultipleFieldStaticConfigSchema,
|
|
49
|
+
]);
|
|
50
|
+
export class SelectMultipleField extends Field {
|
|
51
|
+
static [entityKind] = 'SelectMultipleField';
|
|
52
|
+
checkValuesExist;
|
|
53
|
+
optionsType;
|
|
54
|
+
db;
|
|
55
|
+
/*protected*/ options;
|
|
56
|
+
destinationDb;
|
|
57
|
+
value = undefined;
|
|
58
|
+
_itemIdentifier;
|
|
59
|
+
constructor(config) {
|
|
60
|
+
super(config, 'select_multiple');
|
|
61
|
+
this.checkValuesExist = config.checkValuesExist ?? true;
|
|
62
|
+
this.destinationDb = config.destinationDb;
|
|
63
|
+
/**
|
|
64
|
+
* If options are static, set them
|
|
65
|
+
*/
|
|
66
|
+
if (config.options && config.options?.length > 0) {
|
|
67
|
+
this.options = config.options;
|
|
68
|
+
this.optionsType = 'static';
|
|
69
|
+
this.db = null;
|
|
70
|
+
}
|
|
71
|
+
else if (config.db) {
|
|
72
|
+
/**
|
|
73
|
+
* Else, check if the db config is set
|
|
74
|
+
*/
|
|
75
|
+
this.db = config.db;
|
|
76
|
+
this.optionsType = 'db';
|
|
77
|
+
}
|
|
78
|
+
else if (config.section) {
|
|
79
|
+
/**
|
|
80
|
+
* Else, check if the section config is set
|
|
81
|
+
*/
|
|
82
|
+
/**
|
|
83
|
+
* Runtime validation: Ensure the section is a HasItemsSection or CategorySection
|
|
84
|
+
* TypeScript ensures type safety at compile time, but runtime data (from JSON/APIs)
|
|
85
|
+
* can violate types, so we validate here for safety.
|
|
86
|
+
*/
|
|
87
|
+
const sectionType = config.section.type;
|
|
88
|
+
if (sectionType !== 'has_items' && sectionType !== 'category') {
|
|
89
|
+
const message = `[SelectMultipleField: ${this.label}]: Section must be a HasItemsSection or CategorySection, but got type: ${sectionType ?? 'undefined'}`;
|
|
90
|
+
console.error(chalk.red.bold(message));
|
|
91
|
+
console.error(chalk.yellow('Please make sure the section is a HasItemsSection or CategorySection'));
|
|
92
|
+
throw new Error(message);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Build the section and set the options type to section
|
|
96
|
+
*/
|
|
97
|
+
let section = config.section.build();
|
|
98
|
+
this.optionsType = 'section';
|
|
99
|
+
this.db = {
|
|
100
|
+
table: section.db.table,
|
|
101
|
+
identifier: section.db.identifier.name,
|
|
102
|
+
label: section.headingField.name,
|
|
103
|
+
orderBy: section.db.orderByField ? section.db.orderByField.name : section.db.identifier.name,
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* Set the section instance to undefined to avoid memory leaks
|
|
107
|
+
*/
|
|
108
|
+
section = undefined;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
throw new Error('Select field requires either db, section or an options array.');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
static getEntityKind() {
|
|
115
|
+
return SelectMultipleField[entityKind];
|
|
116
|
+
}
|
|
117
|
+
hasSqlNameAndValue() {
|
|
118
|
+
return this.destinationDb === undefined;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Get the value of the field
|
|
122
|
+
*/
|
|
123
|
+
getValue() {
|
|
124
|
+
return this.value;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Get the value of the field when submitting to the database
|
|
128
|
+
*/
|
|
129
|
+
getSubmitValue() {
|
|
130
|
+
if (!this.hasSqlNameAndValue())
|
|
131
|
+
return;
|
|
132
|
+
/**
|
|
133
|
+
* Set the value as a comma separated string of the values
|
|
134
|
+
*/
|
|
135
|
+
return this.value?.map((value) => value.value).join(',');
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Set the value of the field
|
|
139
|
+
* @param value can be a string of comma-separated values coming from the db table,
|
|
140
|
+
* or an array of `SelectOption` objects coming from the client
|
|
141
|
+
*/
|
|
142
|
+
setValue(value) {
|
|
143
|
+
if (typeof value === 'string') {
|
|
144
|
+
/**
|
|
145
|
+
* Value can be an encoded JSON string sent from the client,
|
|
146
|
+
* try parsing the value as a JSON string
|
|
147
|
+
*/
|
|
148
|
+
let decoded;
|
|
149
|
+
try {
|
|
150
|
+
decoded = JSON.parse(value);
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
decoded = undefined;
|
|
154
|
+
}
|
|
155
|
+
if (!Array.isArray(decoded)) {
|
|
156
|
+
/**
|
|
157
|
+
* If the decoded value is not an array (which means it should be a string coming from the db table),
|
|
158
|
+
* split the values by comma and set the value as an array of objects
|
|
159
|
+
*/
|
|
160
|
+
const values = value.split(',');
|
|
161
|
+
values.map((value) => {
|
|
162
|
+
/**
|
|
163
|
+
* Check if the value exists in the options and return the value and text
|
|
164
|
+
*/
|
|
165
|
+
const option = this.options?.find((option) => option.value.toString() === value.toString());
|
|
166
|
+
if (option) {
|
|
167
|
+
if (!this.value) {
|
|
168
|
+
this.value = [];
|
|
169
|
+
}
|
|
170
|
+
this.value.push(option);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
/**
|
|
176
|
+
* If the decoded value is an array, set the value as the decoded value
|
|
177
|
+
*/
|
|
178
|
+
this.value = decoded;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
this.value = value;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
exportForClient() {
|
|
186
|
+
return {
|
|
187
|
+
...super.exportForClient(),
|
|
188
|
+
options: this.options,
|
|
189
|
+
value: this.value,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
async postSubmit(itemId) {
|
|
193
|
+
if (!this.destinationDb)
|
|
194
|
+
return;
|
|
195
|
+
this._itemIdentifier = itemId;
|
|
196
|
+
try {
|
|
197
|
+
/**
|
|
198
|
+
* Delete the existing values
|
|
199
|
+
*/
|
|
200
|
+
await db.execute(sql `DELETE FROM \`${sql.raw(this.destinationDb.table)}\` WHERE \`${sql.raw(this.destinationDb.itemIdentifier)}\` = ${this._itemIdentifier}`);
|
|
201
|
+
/**
|
|
202
|
+
* Prepare the sql statement
|
|
203
|
+
*/
|
|
204
|
+
const sqlChunks = [];
|
|
205
|
+
sqlChunks.push(sql `INSERT INTO \`${sql.raw(this.destinationDb.table)}\` (\`${sql.raw(this.destinationDb.selectIdentifier)}\`, \`${sql.raw(this.destinationDb.itemIdentifier)}\`) VALUES `);
|
|
206
|
+
/**
|
|
207
|
+
* Loop through the values and add them to the sql statement values
|
|
208
|
+
*/
|
|
209
|
+
if (this.value && this.value.length > 0) {
|
|
210
|
+
const count = this.value.length;
|
|
211
|
+
this.value.forEach((value, index) => {
|
|
212
|
+
sqlChunks.push(sql `(${value.value}, ${this._itemIdentifier})`);
|
|
213
|
+
if (index < count - 1) {
|
|
214
|
+
/**
|
|
215
|
+
* Add a comma to separate the values in the sql statement
|
|
216
|
+
*/
|
|
217
|
+
sqlChunks.push(sql `, `);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Join the sql chunks
|
|
223
|
+
*/
|
|
224
|
+
const finalSql = sql.join(sqlChunks);
|
|
225
|
+
/**
|
|
226
|
+
* Execute the sql statement
|
|
227
|
+
*/
|
|
228
|
+
await db.execute(finalSql);
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
throw new Error(`${this.label}: Error saving values to the destination table`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
async postSubmitRollback() {
|
|
235
|
+
if (!this.destinationDb)
|
|
236
|
+
return;
|
|
237
|
+
if (!this._itemIdentifier) {
|
|
238
|
+
throw new Error(`${this.label}: Item identifier is not set. Make sure to set the item identifier by calling postSubmit() before calling postSubmitRollback()`);
|
|
239
|
+
}
|
|
240
|
+
try {
|
|
241
|
+
await db.execute(sql `DELETE FROM \`${sql.raw(this.destinationDb.table)}\` WHERE \`${sql.raw(this.destinationDb.itemIdentifier)}\` = ${this._itemIdentifier}`);
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
throw new Error(`${this.label}: Error deleting values from the destination table`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Build the field to be used in a form
|
|
249
|
+
*/
|
|
250
|
+
async build() {
|
|
251
|
+
if (this.destinationDb) {
|
|
252
|
+
this.checkDestinationDBConfig();
|
|
253
|
+
await this.checkDestinationDBTableAndColumns();
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* If options are already set, return
|
|
257
|
+
*/
|
|
258
|
+
if (this.options && this.optionsType === 'static')
|
|
259
|
+
return;
|
|
260
|
+
/**
|
|
261
|
+
* Initialize the select db and options
|
|
262
|
+
*/
|
|
263
|
+
this.checkDBConfig();
|
|
264
|
+
await this.checkDBTableAndColumns();
|
|
265
|
+
/**
|
|
266
|
+
* Fetch the options
|
|
267
|
+
*/
|
|
268
|
+
await this.fetchOptions();
|
|
269
|
+
}
|
|
270
|
+
async fetchOptions() {
|
|
271
|
+
/**
|
|
272
|
+
* Let's get the options for the select input
|
|
273
|
+
*/
|
|
274
|
+
let selectStatement = `select * from \`${this.db.table}\` ORDER BY \`${this.db.orderBy ?? this.db.identifier}\` DESC`;
|
|
275
|
+
/**
|
|
276
|
+
* Get the options from the table
|
|
277
|
+
*/
|
|
278
|
+
const selectOptionsRows = await db.execute(sql.raw(selectStatement));
|
|
279
|
+
const rows = selectOptionsRows[0];
|
|
280
|
+
const inputSelectTableIdentifierField = this.db.identifier;
|
|
281
|
+
const inputSelectTableHeadingField = this.db.label;
|
|
282
|
+
/**
|
|
283
|
+
* Set the options
|
|
284
|
+
*/
|
|
285
|
+
this.options = rows.map((row) => ({
|
|
286
|
+
value: row[inputSelectTableIdentifierField],
|
|
287
|
+
label: row[inputSelectTableHeadingField],
|
|
288
|
+
}));
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Check if table and columns exist
|
|
292
|
+
* @private
|
|
293
|
+
*/
|
|
294
|
+
async checkDBTableAndColumns() {
|
|
295
|
+
const columns = await MysqlTableChecker.getColumns(this.db.table);
|
|
296
|
+
if (!columns.includes(this.db.identifier) || !columns.includes(this.db.label)) {
|
|
297
|
+
throw new Error(`Field ${this.label}: Table ${this.db.table} and/or columns: ${this.db.identifier}, ${this.db.label} do not exist in the database!`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Check if destination table and columns exist
|
|
302
|
+
* @private
|
|
303
|
+
*/
|
|
304
|
+
async checkDestinationDBTableAndColumns() {
|
|
305
|
+
if (!this.destinationDb) {
|
|
306
|
+
throw new Error(`Field ${this.label}: Destination table, select identifier and item identifier are required`);
|
|
307
|
+
}
|
|
308
|
+
const columns = await MysqlTableChecker.getColumns(this.destinationDb.table);
|
|
309
|
+
if (!columns.includes(this.destinationDb.selectIdentifier) ||
|
|
310
|
+
!columns.includes(this.destinationDb.itemIdentifier)) {
|
|
311
|
+
throw new Error(`Field ${this.label}: Table ${this.destinationDb.table} and/or columns: ${this.destinationDb.selectIdentifier}, ${this.destinationDb.itemIdentifier} do not exist in the database!`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Check the db config is set
|
|
316
|
+
* @private
|
|
317
|
+
*/
|
|
318
|
+
checkDBConfig() {
|
|
319
|
+
if (this.db.table.trim().length === 0 ||
|
|
320
|
+
this.db.identifier.trim().length === 0 ||
|
|
321
|
+
this.db.label.trim().length === 0) {
|
|
322
|
+
throw new Error(`Field ${this.label}: Select table, identifier and label are required`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Check the destination db config is set
|
|
327
|
+
* @private
|
|
328
|
+
*/
|
|
329
|
+
checkDestinationDBConfig() {
|
|
330
|
+
if (this.destinationDb?.table.trim().length === 0 ||
|
|
331
|
+
this.destinationDb?.selectIdentifier.trim().length === 0 ||
|
|
332
|
+
this.destinationDb?.itemIdentifier.trim().length === 0) {
|
|
333
|
+
throw new Error(`Field ${this.label}: Destination table, select identifier and item identifier are required`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
checkRequired() {
|
|
337
|
+
/**
|
|
338
|
+
* Check if the field is required
|
|
339
|
+
*/
|
|
340
|
+
if (this.required) {
|
|
341
|
+
if (!this.value || this.value.length === 0) {
|
|
342
|
+
throw new Error(`Field ${this.label} is required`);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Prepare the field for submission
|
|
348
|
+
*/
|
|
349
|
+
async prepareForSubmission() {
|
|
350
|
+
if (this.optionsType !== 'static') {
|
|
351
|
+
this.checkDBConfig();
|
|
352
|
+
await this.checkDBTableAndColumns();
|
|
353
|
+
/**
|
|
354
|
+
* Fetch the options
|
|
355
|
+
*/
|
|
356
|
+
await this.fetchOptions();
|
|
357
|
+
}
|
|
358
|
+
if (this.destinationDb) {
|
|
359
|
+
this.checkDestinationDBConfig();
|
|
360
|
+
await this.checkDestinationDBTableAndColumns();
|
|
361
|
+
}
|
|
362
|
+
if (this.checkValuesExist && this.value && this.value.length > 0) {
|
|
363
|
+
/**
|
|
364
|
+
* Check if the values exist in the table
|
|
365
|
+
* This is now safe because we have verified the table and columns exist in the database
|
|
366
|
+
*/
|
|
367
|
+
this.value.forEach((value) => {
|
|
368
|
+
if (!this.options?.find((option) => option.value.toString() === value.value.toString())) {
|
|
369
|
+
throw new Error(`Field ${this.label}: Option ${value.label} does not exist in the options`);
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
const selectMultipleFieldDbOptionsSchema = z.strictObject({
|
|
376
|
+
...baseFieldConfigSchema.shape,
|
|
377
|
+
...selectMultipleFieldDbConfigSchema.shape,
|
|
378
|
+
});
|
|
379
|
+
const selectMultipleFieldSectionOptionsSchema = z.strictObject({
|
|
380
|
+
...baseFieldConfigSchema.shape,
|
|
381
|
+
...selectMultipleFieldSectionConfigSchema.shape,
|
|
382
|
+
});
|
|
383
|
+
const selectMultipleFieldStaticOptionsSchema = z.strictObject({
|
|
384
|
+
...baseFieldConfigSchema.shape,
|
|
385
|
+
...selectMultipleFieldStaticConfigSchema.shape,
|
|
386
|
+
});
|
|
387
|
+
const selectMultipleFieldOptionsSchema = z.union([
|
|
388
|
+
selectMultipleFieldDbOptionsSchema,
|
|
389
|
+
selectMultipleFieldSectionOptionsSchema,
|
|
390
|
+
selectMultipleFieldStaticOptionsSchema,
|
|
391
|
+
]);
|
|
392
|
+
const selectMultipleFieldHelperConfigSchema = z.intersection(selectMultipleFieldOptionsSchema, z.strictObject({
|
|
393
|
+
type: z.literal('select_multiple').describe('The type of the field'),
|
|
394
|
+
build: z
|
|
395
|
+
.function()
|
|
396
|
+
.output(z.instanceof(SelectMultipleField))
|
|
397
|
+
.describe('Build a SelectMultipleField instance from this config'),
|
|
398
|
+
}));
|
|
399
|
+
/**
|
|
400
|
+
* Helper function to create a multiple select field configuration
|
|
401
|
+
* Returns a config object with a build() method that can be serialized and used anywhere.
|
|
402
|
+
*/
|
|
403
|
+
export function selectMultipleField(field) {
|
|
404
|
+
const result = selectMultipleFieldOptionsSchema.safeParse(field);
|
|
405
|
+
if (!result.success) {
|
|
406
|
+
throw new Error(`[Field: ${field.name}]: ${z.prettifyError(result.error)}`);
|
|
407
|
+
}
|
|
408
|
+
const parsedField = result.data;
|
|
409
|
+
const config = {
|
|
410
|
+
...parsedField,
|
|
411
|
+
type: 'select_multiple',
|
|
412
|
+
build() {
|
|
413
|
+
return new SelectMultipleField(parsedField);
|
|
414
|
+
},
|
|
415
|
+
};
|
|
416
|
+
return config;
|
|
417
|
+
}
|