webspresso 0.0.14 ā 0.0.16
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 +3 -0
- package/bin/commands/admin-setup.js +72 -0
- package/bin/webspresso.js +2 -0
- package/core/orm/index.js +99 -1
- package/index.js +3 -0
- package/package.json +3 -1
- package/plugins/admin-panel/admin-user-model.js +3 -3
- package/plugins/admin-panel/api.js +1 -1
- package/plugins/admin-panel/index.js +28 -34
package/README.md
CHANGED
|
@@ -1007,6 +1007,9 @@ webspresso db:make create_posts_table
|
|
|
1007
1007
|
|
|
1008
1008
|
# Create migration from model (scaffolding)
|
|
1009
1009
|
webspresso db:make create_users_table --model User
|
|
1010
|
+
|
|
1011
|
+
# Admin Panel Setup
|
|
1012
|
+
webspresso admin:setup # Create admin_users migration
|
|
1010
1013
|
```
|
|
1011
1014
|
|
|
1012
1015
|
**Database Config File (`webspresso.db.js`):**
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin Setup Command
|
|
3
|
+
* Create admin_users table migration for admin panel
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const { loadDbConfig } = require('../utils/db');
|
|
9
|
+
const { generateAdminUsersMigration } = require('../../plugins/admin-panel/migration-template');
|
|
10
|
+
|
|
11
|
+
function registerCommand(program) {
|
|
12
|
+
program
|
|
13
|
+
.command('admin:setup')
|
|
14
|
+
.description('Create admin_users table migration for admin panel')
|
|
15
|
+
.option('-c, --config <path>', 'Path to database config file')
|
|
16
|
+
.action(async (options) => {
|
|
17
|
+
try {
|
|
18
|
+
const { config, path: configPath } = loadDbConfig(options.config);
|
|
19
|
+
console.log(`\nš¦ Using config: ${configPath}\n`);
|
|
20
|
+
|
|
21
|
+
const migrationDir = config.migrations?.directory || './migrations';
|
|
22
|
+
|
|
23
|
+
// Ensure migrations directory exists
|
|
24
|
+
if (!fs.existsSync(migrationDir)) {
|
|
25
|
+
fs.mkdirSync(migrationDir, { recursive: true });
|
|
26
|
+
console.log(`Created directory: ${migrationDir}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Check if migration already exists
|
|
30
|
+
const existingMigrations = fs.readdirSync(migrationDir)
|
|
31
|
+
.filter(f => f.includes('admin_users') || f.includes('admin-users'));
|
|
32
|
+
|
|
33
|
+
if (existingMigrations.length > 0) {
|
|
34
|
+
console.log(`ā ļø Admin users migration already exists:`);
|
|
35
|
+
existingMigrations.forEach(m => console.log(` - ${m}`));
|
|
36
|
+
console.log(`\n If you want to recreate it, delete the existing migration first.\n`);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Generate filename with timestamp
|
|
41
|
+
const now = new Date();
|
|
42
|
+
const timestamp = [
|
|
43
|
+
now.getFullYear(),
|
|
44
|
+
String(now.getMonth() + 1).padStart(2, '0'),
|
|
45
|
+
String(now.getDate()).padStart(2, '0'),
|
|
46
|
+
'_',
|
|
47
|
+
String(now.getHours()).padStart(2, '0'),
|
|
48
|
+
String(now.getMinutes()).padStart(2, '0'),
|
|
49
|
+
String(now.getSeconds()).padStart(2, '0'),
|
|
50
|
+
].join('');
|
|
51
|
+
|
|
52
|
+
const filename = `${timestamp}_create_admin_users_table.js`;
|
|
53
|
+
const filepath = path.join(migrationDir, filename);
|
|
54
|
+
|
|
55
|
+
// Generate migration content
|
|
56
|
+
const content = generateAdminUsersMigration();
|
|
57
|
+
|
|
58
|
+
// Write migration file
|
|
59
|
+
fs.writeFileSync(filepath, content);
|
|
60
|
+
|
|
61
|
+
console.log(`ā
Created admin users migration: ${filepath}\n`);
|
|
62
|
+
console.log(`š Next steps:`);
|
|
63
|
+
console.log(` 1. Run migration: webspresso db:migrate`);
|
|
64
|
+
console.log(` 2. Create first admin user via admin panel setup page\n`);
|
|
65
|
+
} catch (err) {
|
|
66
|
+
console.error('ā Error:', err.message);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = { registerCommand };
|
package/bin/webspresso.js
CHANGED
|
@@ -24,6 +24,7 @@ const { registerCommand: registerDbRollback } = require('./commands/db-rollback'
|
|
|
24
24
|
const { registerCommand: registerDbStatus } = require('./commands/db-status');
|
|
25
25
|
const { registerCommand: registerDbMake } = require('./commands/db-make');
|
|
26
26
|
const { registerCommand: registerSeed } = require('./commands/seed');
|
|
27
|
+
const { registerCommand: registerAdminSetup } = require('./commands/admin-setup');
|
|
27
28
|
|
|
28
29
|
registerNew(program);
|
|
29
30
|
registerPage(program);
|
|
@@ -36,6 +37,7 @@ registerDbRollback(program);
|
|
|
36
37
|
registerDbStatus(program);
|
|
37
38
|
registerDbMake(program);
|
|
38
39
|
registerSeed(program);
|
|
40
|
+
registerAdminSetup(program);
|
|
39
41
|
|
|
40
42
|
// Parse arguments
|
|
41
43
|
program.parse();
|
package/core/orm/index.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* @module core/orm
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
const path = require('path');
|
|
7
8
|
const { createSchemaHelpers, extractColumnsFromSchema, getColumnMeta } = require('./schema-helpers');
|
|
8
9
|
const { defineModel, getModel, getAllModels, hasModel, clearRegistry } = require('./model');
|
|
9
10
|
|
|
@@ -40,8 +41,105 @@ function createDatabase(config) {
|
|
|
40
41
|
throw new Error('Knex is required for ORM. Install it with: npm install knex');
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
// Check if database driver is available in project's node_modules
|
|
45
|
+
const client = config.client;
|
|
46
|
+
if (client) {
|
|
47
|
+
const driverMap = {
|
|
48
|
+
'better-sqlite3': 'better-sqlite3',
|
|
49
|
+
'pg': 'pg',
|
|
50
|
+
'mysql2': 'mysql2',
|
|
51
|
+
'mysql': 'mysql2',
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const driverName = driverMap[client] || client;
|
|
55
|
+
|
|
56
|
+
// Try to resolve and pre-load the driver from project's node_modules
|
|
57
|
+
// This ensures Knex can find it when it tries to load it
|
|
58
|
+
let driverPath = null;
|
|
59
|
+
const resolvePaths = [
|
|
60
|
+
path.join(process.cwd(), 'node_modules'),
|
|
61
|
+
process.cwd(),
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
for (const resolvePath of resolvePaths) {
|
|
65
|
+
try {
|
|
66
|
+
driverPath = require.resolve(driverName, { paths: [resolvePath] });
|
|
67
|
+
// Pre-load the driver so Knex can find it in Module._cache
|
|
68
|
+
require(driverPath);
|
|
69
|
+
break;
|
|
70
|
+
} catch (e) {
|
|
71
|
+
// Continue to next path
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// If still not found, provide helpful error
|
|
76
|
+
if (!driverPath) {
|
|
77
|
+
const installCmd = driverName === 'better-sqlite3'
|
|
78
|
+
? 'npm install better-sqlite3 --save'
|
|
79
|
+
: driverName === 'pg'
|
|
80
|
+
? 'npm install pg --save'
|
|
81
|
+
: driverName === 'mysql2'
|
|
82
|
+
? 'npm install mysql2 --save'
|
|
83
|
+
: `npm install ${driverName} --save`;
|
|
84
|
+
|
|
85
|
+
throw new Error(
|
|
86
|
+
`Database driver "${driverName}" is not installed in your project. ` +
|
|
87
|
+
`Please install it with: ${installCmd}\n` +
|
|
88
|
+
`Note: Database drivers are peer dependencies and must be installed in your project's node_modules, not globally.\n` +
|
|
89
|
+
`Current working directory: ${process.cwd()}`
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
43
94
|
// Create Knex instance
|
|
44
|
-
|
|
95
|
+
// Knex will try to load the driver from its own node_modules
|
|
96
|
+
// We need to ensure the driver is available in the project's node_modules
|
|
97
|
+
let knexInstance;
|
|
98
|
+
try {
|
|
99
|
+
knexInstance = knex(config);
|
|
100
|
+
} catch (e) {
|
|
101
|
+
// If knex throws an error about missing driver, provide better message
|
|
102
|
+
if (e.message && (e.message.includes('Cannot find module') || e.message.includes('run') || e.message.includes('npm install'))) {
|
|
103
|
+
const driverName = config.client;
|
|
104
|
+
const installCmd = driverName === 'better-sqlite3'
|
|
105
|
+
? 'npm install better-sqlite3 --save'
|
|
106
|
+
: driverName === 'pg'
|
|
107
|
+
? 'npm install pg --save'
|
|
108
|
+
: driverName === 'mysql2'
|
|
109
|
+
? 'npm install mysql2 --save'
|
|
110
|
+
: `npm install ${driverName} --save`;
|
|
111
|
+
|
|
112
|
+
// Check if driver exists in project's node_modules
|
|
113
|
+
let driverExists = false;
|
|
114
|
+
try {
|
|
115
|
+
require.resolve(driverName, { paths: [path.join(process.cwd(), 'node_modules')] });
|
|
116
|
+
driverExists = true;
|
|
117
|
+
} catch (resolveError) {
|
|
118
|
+
// Driver not found in project
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (!driverExists) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`Database driver "${driverName}" is not installed in your project. ` +
|
|
124
|
+
`Please install it with: ${installCmd}\n` +
|
|
125
|
+
`Note: Database drivers are peer dependencies and must be installed in your project's node_modules, not globally.\n` +
|
|
126
|
+
`Current working directory: ${process.cwd()}\n` +
|
|
127
|
+
`Make sure you run "${installCmd}" in your project directory.`
|
|
128
|
+
);
|
|
129
|
+
} else {
|
|
130
|
+
// Driver exists but Knex can't find it - this is a module resolution issue
|
|
131
|
+
throw new Error(
|
|
132
|
+
`Database driver "${driverName}" is installed but Knex cannot find it. ` +
|
|
133
|
+
`This might be a module resolution issue. Try:\n` +
|
|
134
|
+
`1. Delete node_modules and package-lock.json\n` +
|
|
135
|
+
`2. Run "npm install" again\n` +
|
|
136
|
+
`3. Make sure "${driverName}" is in your package.json dependencies\n` +
|
|
137
|
+
`Original error: ${e.message}`
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
throw e;
|
|
142
|
+
}
|
|
45
143
|
|
|
46
144
|
// Create migration manager
|
|
47
145
|
const migrationConfig = config.migrations || {};
|
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webspresso",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.16",
|
|
4
4
|
"description": "Minimal, production-ready SSR framework for Node.js with file-based routing, Nunjucks templating, built-in i18n, and CLI tooling",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -37,9 +37,11 @@
|
|
|
37
37
|
"plugins/"
|
|
38
38
|
],
|
|
39
39
|
"dependencies": {
|
|
40
|
+
"bcrypt": "^5.1.1",
|
|
40
41
|
"commander": "^11.1.0",
|
|
41
42
|
"dayjs": "^1.11.19",
|
|
42
43
|
"express": "^4.18.2",
|
|
44
|
+
"express-session": "^1.18.0",
|
|
43
45
|
"helmet": "^7.2.0",
|
|
44
46
|
"inquirer": "^8.2.6",
|
|
45
47
|
"knex": "^3.1.0",
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* @module plugins/admin-panel/admin-user-model
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
const { defineModel } = require('
|
|
8
|
-
const { zdb } = require('
|
|
7
|
+
const { defineModel } = require('../../core/orm/model');
|
|
8
|
+
const { zdb } = require('../../core/orm');
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* AdminUser model schema
|
|
@@ -23,7 +23,7 @@ const AdminUserSchema = zdb.schema({
|
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Create and register AdminUser model
|
|
26
|
-
* @returns {import('
|
|
26
|
+
* @returns {import('../../core/orm/types').ModelDefinition}
|
|
27
27
|
*/
|
|
28
28
|
function createAdminUserModel() {
|
|
29
29
|
return defineModel({
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @module plugins/admin-panel/api
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
const { getAllModels, getModel } = require('
|
|
7
|
+
const { getAllModels, getModel } = require('../../core/orm/model');
|
|
8
8
|
const { checkAdminExists, setupAdmin, login, logout, requireAuth } = require('./auth');
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -31,25 +31,9 @@ function adminPanelPlugin(options = {}) {
|
|
|
31
31
|
throw new Error('Admin panel plugin requires a database instance. Pass `db` in options.');
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
try {
|
|
39
|
-
session = require('express-session');
|
|
40
|
-
} catch (e) {
|
|
41
|
-
throw new Error(
|
|
42
|
-
'Admin panel plugin requires express-session. Install it with: npm install express-session'
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
try {
|
|
47
|
-
bcrypt = require('bcrypt');
|
|
48
|
-
} catch (e) {
|
|
49
|
-
throw new Error(
|
|
50
|
-
'Admin panel plugin requires bcrypt. Install it with: npm install bcrypt'
|
|
51
|
-
);
|
|
52
|
-
}
|
|
34
|
+
// Dependencies are now in main package
|
|
35
|
+
const session = require('express-session');
|
|
36
|
+
const bcrypt = require('bcrypt');
|
|
53
37
|
|
|
54
38
|
return {
|
|
55
39
|
name: 'admin-panel',
|
|
@@ -61,8 +45,13 @@ function adminPanelPlugin(options = {}) {
|
|
|
61
45
|
* Register hook - called when plugin is registered
|
|
62
46
|
*/
|
|
63
47
|
async register(ctx) {
|
|
64
|
-
// Create and register AdminUser model
|
|
65
|
-
const
|
|
48
|
+
// Create and register AdminUser model (only if not already registered)
|
|
49
|
+
const { getModel } = require('../../core/orm/model');
|
|
50
|
+
let AdminUser = getModel('AdminUser');
|
|
51
|
+
|
|
52
|
+
if (!AdminUser) {
|
|
53
|
+
AdminUser = createAdminUserModel();
|
|
54
|
+
}
|
|
66
55
|
|
|
67
56
|
// Store in plugin context for later use
|
|
68
57
|
this._adminUser = AdminUser;
|
|
@@ -86,19 +75,24 @@ function adminPanelPlugin(options = {}) {
|
|
|
86
75
|
const bcrypt = this._bcrypt;
|
|
87
76
|
const session = this._session;
|
|
88
77
|
|
|
89
|
-
// Setup session middleware
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
78
|
+
// Setup session middleware (only once, even if multiple plugins)
|
|
79
|
+
// Check if session middleware is already registered
|
|
80
|
+
if (!app._webspressoSessionInitialized) {
|
|
81
|
+
const sessionSecret = options.sessionSecret || process.env.SESSION_SECRET || 'webspresso-admin-secret-change-in-production';
|
|
82
|
+
|
|
83
|
+
app.use(session({
|
|
84
|
+
secret: sessionSecret,
|
|
85
|
+
resave: false,
|
|
86
|
+
saveUninitialized: false,
|
|
87
|
+
cookie: {
|
|
88
|
+
secure: process.env.NODE_ENV === 'production',
|
|
89
|
+
httpOnly: true,
|
|
90
|
+
maxAge: 24 * 60 * 60 * 1000, // 24 hours
|
|
91
|
+
},
|
|
92
|
+
}));
|
|
93
|
+
|
|
94
|
+
app._webspressoSessionInitialized = true;
|
|
95
|
+
}
|
|
102
96
|
|
|
103
97
|
// Create API handlers
|
|
104
98
|
const apiHandlers = createApiHandlers({
|