webspresso 0.0.13 → 0.0.14
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/README.md +4 -7
- package/bin/commands/add-tailwind.js +151 -0
- package/bin/commands/api.js +70 -0
- package/bin/commands/db-make.js +76 -0
- package/bin/commands/db-migrate.js +43 -0
- package/bin/commands/db-rollback.js +48 -0
- package/bin/commands/db-status.js +53 -0
- package/bin/commands/dev.js +73 -0
- package/bin/commands/new.js +634 -0
- package/bin/commands/page.js +134 -0
- package/bin/commands/seed.js +154 -0
- package/bin/commands/start.js +30 -0
- package/bin/utils/db.js +54 -0
- package/bin/utils/migration.js +36 -0
- package/bin/utils/project.js +97 -0
- package/bin/utils/seed.js +112 -0
- package/bin/webspresso.js +24 -1696
- package/core/orm/index.js +14 -1
- package/core/orm/migrations/scaffold.js +5 -0
- package/core/orm/model.js +8 -0
- package/core/orm/schema-helpers.js +39 -1
- package/core/orm/seeder.js +56 -3
- package/core/orm/types.js +28 -1
- package/index.js +2 -1
- package/package.json +1 -1
- package/plugins/admin-panel/admin-user-model.js +42 -0
- package/plugins/admin-panel/api.js +436 -0
- package/plugins/admin-panel/app.js +68 -0
- package/plugins/admin-panel/auth.js +157 -0
- package/plugins/admin-panel/components.js +359 -0
- package/plugins/admin-panel/field-renderers/array.js +57 -0
- package/plugins/admin-panel/field-renderers/basic.js +205 -0
- package/plugins/admin-panel/field-renderers/file-upload.js +124 -0
- package/plugins/admin-panel/field-renderers/index.js +93 -0
- package/plugins/admin-panel/field-renderers/json.js +52 -0
- package/plugins/admin-panel/field-renderers/relations.js +96 -0
- package/plugins/admin-panel/field-renderers/rich-text.js +83 -0
- package/plugins/admin-panel/index.js +187 -0
- package/plugins/admin-panel/migration-template.js +39 -0
- package/plugins/admin-panel/styles.js +9 -0
- package/plugins/index.js +2 -0
package/core/orm/index.js
CHANGED
|
@@ -6,6 +6,18 @@
|
|
|
6
6
|
|
|
7
7
|
const { createSchemaHelpers, extractColumnsFromSchema, getColumnMeta } = require('./schema-helpers');
|
|
8
8
|
const { defineModel, getModel, getAllModels, hasModel, clearRegistry } = require('./model');
|
|
9
|
+
|
|
10
|
+
// Create zdb instance with zod (zod is a dependency)
|
|
11
|
+
let z;
|
|
12
|
+
try {
|
|
13
|
+
z = require('zod');
|
|
14
|
+
} catch {
|
|
15
|
+
// Zod not installed, zdb will be undefined
|
|
16
|
+
z = null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Export zdb instance directly
|
|
20
|
+
const zdb = z ? createSchemaHelpers(z) : null;
|
|
9
21
|
const { createRepository } = require('./repository');
|
|
10
22
|
const { createQueryBuilder, QueryBuilder } = require('./query-builder');
|
|
11
23
|
const { runTransaction, createTransactionContext } = require('./transaction');
|
|
@@ -110,7 +122,8 @@ module.exports = {
|
|
|
110
122
|
// Main factory
|
|
111
123
|
createDatabase,
|
|
112
124
|
|
|
113
|
-
// Schema helpers
|
|
125
|
+
// Schema helpers - zdb instance (direct export)
|
|
126
|
+
zdb,
|
|
114
127
|
createSchemaHelpers,
|
|
115
128
|
extractColumnsFromSchema,
|
|
116
129
|
getColumnMeta,
|
|
@@ -142,6 +142,11 @@ function generateColumnLine(columnName, meta) {
|
|
|
142
142
|
parts.push(`table.json('${columnName}')`);
|
|
143
143
|
break;
|
|
144
144
|
|
|
145
|
+
case 'array':
|
|
146
|
+
// Array is stored as JSON in database
|
|
147
|
+
parts.push(`table.json('${columnName}')`);
|
|
148
|
+
break;
|
|
149
|
+
|
|
145
150
|
case 'enum':
|
|
146
151
|
const enumValues = meta.enumValues || [];
|
|
147
152
|
const valuesStr = enumValues.map(v => `'${v}'`).join(', ');
|
package/core/orm/model.js
CHANGED
|
@@ -25,6 +25,7 @@ function defineModel(options) {
|
|
|
25
25
|
primaryKey = 'id',
|
|
26
26
|
relations = {},
|
|
27
27
|
scopes = {},
|
|
28
|
+
admin = {},
|
|
28
29
|
} = options;
|
|
29
30
|
|
|
30
31
|
// Validate required fields
|
|
@@ -78,6 +79,13 @@ function defineModel(options) {
|
|
|
78
79
|
tenant: scopes.tenant || null,
|
|
79
80
|
},
|
|
80
81
|
columns,
|
|
82
|
+
admin: {
|
|
83
|
+
enabled: admin.enabled || false,
|
|
84
|
+
label: admin.label || name,
|
|
85
|
+
icon: admin.icon || null,
|
|
86
|
+
customFields: admin.customFields || {},
|
|
87
|
+
queries: admin.queries || {},
|
|
88
|
+
},
|
|
81
89
|
};
|
|
82
90
|
|
|
83
91
|
// Register model
|
|
@@ -67,7 +67,15 @@ function createSchemaHelpers(z) {
|
|
|
67
67
|
return schema.describe(encodeColumnMeta(meta));
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
const helpers = {
|
|
71
|
+
/**
|
|
72
|
+
* Create a Zod object schema with database metadata
|
|
73
|
+
* @param {Object} shape - Object shape with zdb fields
|
|
74
|
+
* @returns {import('zod').ZodObject}
|
|
75
|
+
*/
|
|
76
|
+
schema(shape) {
|
|
77
|
+
return z.object(shape);
|
|
78
|
+
},
|
|
71
79
|
/**
|
|
72
80
|
* Primary key column (bigint, auto-increment)
|
|
73
81
|
* @param {Partial<import('./types').ColumnMeta>} [options={}]
|
|
@@ -304,6 +312,34 @@ function createSchemaHelpers(z) {
|
|
|
304
312
|
});
|
|
305
313
|
},
|
|
306
314
|
|
|
315
|
+
/**
|
|
316
|
+
* Array column (stored as JSON in database)
|
|
317
|
+
* @param {import('zod').ZodTypeAny} [itemSchema] - Schema for array items (default: z.any())
|
|
318
|
+
* @param {Partial<import('./types').ColumnMeta>} [options={}]
|
|
319
|
+
* @returns {import('zod').ZodArray}
|
|
320
|
+
*/
|
|
321
|
+
array(itemSchema, options = {}) {
|
|
322
|
+
// If first argument is options object (backward compatibility)
|
|
323
|
+
if (itemSchema && typeof itemSchema === 'object' && !itemSchema._def) {
|
|
324
|
+
options = itemSchema;
|
|
325
|
+
itemSchema = z.any();
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const { nullable = false, ...rest } = options;
|
|
329
|
+
const baseItemSchema = itemSchema || z.any();
|
|
330
|
+
let schema = z.array(baseItemSchema);
|
|
331
|
+
|
|
332
|
+
if (nullable) {
|
|
333
|
+
schema = schema.nullable().optional();
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return withMeta(schema, {
|
|
337
|
+
type: 'array',
|
|
338
|
+
nullable,
|
|
339
|
+
...rest,
|
|
340
|
+
});
|
|
341
|
+
},
|
|
342
|
+
|
|
307
343
|
/**
|
|
308
344
|
* Enum column
|
|
309
345
|
* @param {string[]} values - Allowed enum values
|
|
@@ -370,6 +406,8 @@ function createSchemaHelpers(z) {
|
|
|
370
406
|
});
|
|
371
407
|
},
|
|
372
408
|
};
|
|
409
|
+
|
|
410
|
+
return helpers;
|
|
373
411
|
}
|
|
374
412
|
|
|
375
413
|
/**
|
package/core/orm/seeder.js
CHANGED
|
@@ -61,6 +61,11 @@ function createSeeder(faker, knex) {
|
|
|
61
61
|
// Smart field detection by name
|
|
62
62
|
const lowerName = columnName.toLowerCase();
|
|
63
63
|
|
|
64
|
+
// Array type should be handled in generateByType (skip smart detection for arrays)
|
|
65
|
+
if (meta.type === 'array') {
|
|
66
|
+
return generateByType(columnName, meta);
|
|
67
|
+
}
|
|
68
|
+
|
|
64
69
|
// Email detection
|
|
65
70
|
if (lowerName.includes('email')) {
|
|
66
71
|
return faker.internet.email().toLowerCase();
|
|
@@ -146,16 +151,17 @@ function createSeeder(faker, knex) {
|
|
|
146
151
|
return faker.helpers.arrayElement(['active', 'inactive', 'pending']);
|
|
147
152
|
}
|
|
148
153
|
|
|
149
|
-
// Generate by type
|
|
150
|
-
return generateByType(meta);
|
|
154
|
+
// Generate by type (pass columnName for array type detection)
|
|
155
|
+
return generateByType(columnName, meta);
|
|
151
156
|
}
|
|
152
157
|
|
|
153
158
|
/**
|
|
154
159
|
* Generate value by column type
|
|
160
|
+
* @param {string} columnName - Column name (for smart detection, especially for arrays)
|
|
155
161
|
* @param {Object} meta - Column metadata
|
|
156
162
|
* @returns {*} Generated value
|
|
157
163
|
*/
|
|
158
|
-
function generateByType(meta) {
|
|
164
|
+
function generateByType(columnName, meta) {
|
|
159
165
|
switch (meta.type) {
|
|
160
166
|
case 'bigint':
|
|
161
167
|
case 'integer':
|
|
@@ -186,6 +192,53 @@ function createSeeder(faker, knex) {
|
|
|
186
192
|
case 'json':
|
|
187
193
|
return { key: faker.lorem.word(), value: faker.lorem.sentence() };
|
|
188
194
|
|
|
195
|
+
case 'array':
|
|
196
|
+
// Generate a random array with 1-5 items
|
|
197
|
+
const arrayLength = faker.number.int({ min: 1, max: 5 });
|
|
198
|
+
const arrayItems = [];
|
|
199
|
+
|
|
200
|
+
// Try to infer item type from column name
|
|
201
|
+
const lowerName = (columnName || '').toLowerCase();
|
|
202
|
+
|
|
203
|
+
// String arrays (tags, categories, etc.)
|
|
204
|
+
if (lowerName.includes('tag') || lowerName.includes('category') ||
|
|
205
|
+
lowerName.includes('label') || lowerName.includes('keyword')) {
|
|
206
|
+
for (let i = 0; i < arrayLength; i++) {
|
|
207
|
+
arrayItems.push(faker.lorem.word());
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// Number arrays (ids, scores, etc.)
|
|
211
|
+
else if (lowerName.includes('id') || lowerName.includes('score') ||
|
|
212
|
+
lowerName.includes('price') || lowerName.includes('amount')) {
|
|
213
|
+
for (let i = 0; i < arrayLength; i++) {
|
|
214
|
+
arrayItems.push(faker.number.int({ min: 1, max: 100 }));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// Email arrays
|
|
218
|
+
else if (lowerName.includes('email')) {
|
|
219
|
+
for (let i = 0; i < arrayLength; i++) {
|
|
220
|
+
arrayItems.push(faker.internet.email().toLowerCase());
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// URL arrays
|
|
224
|
+
else if (lowerName.includes('url') || lowerName.includes('link')) {
|
|
225
|
+
for (let i = 0; i < arrayLength; i++) {
|
|
226
|
+
arrayItems.push(faker.internet.url());
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
// Default: mixed array (strings and numbers)
|
|
230
|
+
else {
|
|
231
|
+
for (let i = 0; i < arrayLength; i++) {
|
|
232
|
+
if (faker.datatype.boolean()) {
|
|
233
|
+
arrayItems.push(faker.lorem.word());
|
|
234
|
+
} else {
|
|
235
|
+
arrayItems.push(faker.number.int({ min: 1, max: 100 }));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return arrayItems;
|
|
241
|
+
|
|
189
242
|
case 'enum':
|
|
190
243
|
if (meta.enumValues && meta.enumValues.length > 0) {
|
|
191
244
|
return faker.helpers.arrayElement(meta.enumValues);
|
package/core/orm/types.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// ============================================================================
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* @typedef {'id'|'string'|'text'|'integer'|'bigint'|'float'|'decimal'|'boolean'|'date'|'datetime'|'timestamp'|'json'|'enum'|'uuid'} ColumnType
|
|
12
|
+
* @typedef {'id'|'string'|'text'|'integer'|'bigint'|'float'|'decimal'|'boolean'|'date'|'datetime'|'timestamp'|'json'|'array'|'enum'|'uuid'} ColumnType
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
/**
|
|
@@ -75,6 +75,31 @@
|
|
|
75
75
|
* @property {boolean} [onlyTrashed=false] - Only soft-deleted records
|
|
76
76
|
*/
|
|
77
77
|
|
|
78
|
+
// ============================================================================
|
|
79
|
+
// Admin Panel Types
|
|
80
|
+
// ============================================================================
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @typedef {Object} CustomFieldConfig
|
|
84
|
+
* @property {string} type - Field renderer type (e.g., 'file-upload', 'rich-text')
|
|
85
|
+
* @property {*} [options] - Additional options for the field renderer
|
|
86
|
+
*/
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @typedef {Function} QueryConfig
|
|
90
|
+
* @param {import('./repository').Repository} repo - Repository instance
|
|
91
|
+
* @returns {Promise<*>} Query result
|
|
92
|
+
*/
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @typedef {Object} AdminMetadata
|
|
96
|
+
* @property {boolean} [enabled=false] - Whether model is enabled in admin panel
|
|
97
|
+
* @property {string} [label] - Display label for the model (default: model name)
|
|
98
|
+
* @property {string} [icon] - Icon for the model (emoji or icon name)
|
|
99
|
+
* @property {Object.<string, CustomFieldConfig>} [customFields={}] - Custom field configurations
|
|
100
|
+
* @property {Object.<string, QueryConfig>} [queries={}] - Custom query functions
|
|
101
|
+
*/
|
|
102
|
+
|
|
78
103
|
// ============================================================================
|
|
79
104
|
// Model Types
|
|
80
105
|
// ============================================================================
|
|
@@ -87,6 +112,7 @@
|
|
|
87
112
|
* @property {string} [primaryKey='id'] - Primary key column name
|
|
88
113
|
* @property {RelationsMap} [relations={}] - Relation definitions
|
|
89
114
|
* @property {ScopeOptions} [scopes={}] - Scope options
|
|
115
|
+
* @property {AdminMetadata} [admin] - Admin panel metadata
|
|
90
116
|
*/
|
|
91
117
|
|
|
92
118
|
/**
|
|
@@ -98,6 +124,7 @@
|
|
|
98
124
|
* @property {RelationsMap} relations - Relation definitions
|
|
99
125
|
* @property {ScopeOptions} scopes - Scope options
|
|
100
126
|
* @property {Map<string, ColumnMeta>} columns - Parsed column metadata
|
|
127
|
+
* @property {AdminMetadata} [admin] - Admin panel metadata
|
|
101
128
|
*/
|
|
102
129
|
|
|
103
130
|
// ============================================================================
|
package/index.js
CHANGED
|
@@ -30,7 +30,7 @@ const {
|
|
|
30
30
|
const orm = require('./core/orm');
|
|
31
31
|
|
|
32
32
|
// Built-in plugins
|
|
33
|
-
const { schemaExplorerPlugin } = require('./plugins');
|
|
33
|
+
const { schemaExplorerPlugin, adminPanelPlugin } = require('./plugins');
|
|
34
34
|
|
|
35
35
|
module.exports = {
|
|
36
36
|
// Main API
|
|
@@ -65,5 +65,6 @@ module.exports = {
|
|
|
65
65
|
|
|
66
66
|
// Plugins
|
|
67
67
|
schemaExplorerPlugin,
|
|
68
|
+
adminPanelPlugin,
|
|
68
69
|
};
|
|
69
70
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin User Model
|
|
3
|
+
* Model definition for admin panel users
|
|
4
|
+
* @module plugins/admin-panel/admin-user-model
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { defineModel } = require('../../../core/orm/model');
|
|
8
|
+
const { zdb } = require('../../../core/orm');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* AdminUser model schema
|
|
12
|
+
*/
|
|
13
|
+
const AdminUserSchema = zdb.schema({
|
|
14
|
+
id: zdb.id(),
|
|
15
|
+
email: zdb.string({ unique: true, maxLength: 255 }),
|
|
16
|
+
password: zdb.string({ maxLength: 255 }), // Hashed password
|
|
17
|
+
name: zdb.string({ maxLength: 255 }),
|
|
18
|
+
role: zdb.string({ maxLength: 50, default: 'admin' }),
|
|
19
|
+
active: zdb.boolean({ default: true }),
|
|
20
|
+
created_at: zdb.timestamp({ auto: 'create' }),
|
|
21
|
+
updated_at: zdb.timestamp({ auto: 'update' }),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Create and register AdminUser model
|
|
26
|
+
* @returns {import('../../../core/orm/types').ModelDefinition}
|
|
27
|
+
*/
|
|
28
|
+
function createAdminUserModel() {
|
|
29
|
+
return defineModel({
|
|
30
|
+
name: 'AdminUser',
|
|
31
|
+
table: 'admin_users',
|
|
32
|
+
schema: AdminUserSchema,
|
|
33
|
+
scopes: {
|
|
34
|
+
timestamps: true,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = {
|
|
40
|
+
createAdminUserModel,
|
|
41
|
+
AdminUserSchema,
|
|
42
|
+
};
|