db-model-router 1.0.6 → 1.0.7
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 +150 -11
- package/TODO.md +0 -15
- package/db-manager/.dbmanager.sqlite +0 -0
- package/db-manager/README.md +223 -0
- package/db-manager/adapter-proxy.js +361 -0
- package/db-manager/demo/cockroachdb.env +6 -0
- package/db-manager/demo/demo.sqlite +0 -0
- package/db-manager/demo/dynamodb.env +7 -0
- package/db-manager/demo/mongodb.env +4 -0
- package/db-manager/demo/mssql.env +6 -0
- package/db-manager/demo/mysql.env +6 -0
- package/db-manager/demo/oracle.env +6 -0
- package/db-manager/demo/postgres.env +6 -0
- package/db-manager/demo/redis.env +4 -0
- package/db-manager/demo/seeds/cockroachdb.sql +32 -0
- package/db-manager/demo/seeds/mssql.sql +32 -0
- package/db-manager/demo/seeds/mysql.sql +32 -0
- package/db-manager/demo/seeds/oracle.sql +43 -0
- package/db-manager/demo/seeds/postgres.sql +32 -0
- package/db-manager/demo/seeds/sqlite3.sql +32 -0
- package/db-manager/demo/sqlite3.env +2 -0
- package/db-manager/metadata-db.js +170 -0
- package/db-manager/public/.gitkeep +1 -0
- package/db-manager/public/css/style.css +1413 -0
- package/db-manager/public/js/app.js +1370 -0
- package/db-manager/routes/api.js +388 -0
- package/db-manager/routes/views.js +61 -0
- package/db-manager/server.js +39 -0
- package/db-manager/utils/build-filter-config.js +18 -0
- package/db-manager/utils/csv-export.js +59 -0
- package/db-manager/utils/export-filename.js +39 -0
- package/db-manager/utils/filter-tables.js +20 -0
- package/db-manager/utils/parse-filters.js +93 -0
- package/db-manager/utils/sort-state.js +35 -0
- package/db-manager/views/.gitkeep +1 -0
- package/db-manager/views/dashboard.ejs +53 -0
- package/db-manager/views/history.ejs +52 -0
- package/db-manager/views/index.ejs +35 -0
- package/db-manager/views/layout.ejs +31 -0
- package/db-manager/views/partials/data-panel.ejs +74 -0
- package/db-manager/views/partials/header.ejs +36 -0
- package/db-manager/views/partials/sidebar.ejs +30 -0
- package/db-manager/views/query.ejs +58 -0
- package/dbmr.schema.json +22 -44
- package/demo/.dockerignore +7 -0
- package/demo/.env.example +14 -0
- package/demo/Dockerfile +20 -0
- package/demo/app.js +39 -0
- package/demo/commons/add_migration.js +43 -0
- package/demo/commons/db.js +28 -0
- package/demo/commons/migrate.js +68 -0
- package/demo/commons/modules.js +18 -0
- package/demo/commons/password.js +36 -0
- package/demo/commons/security.js +30 -0
- package/demo/commons/session.js +13 -0
- package/demo/commons/webhook.js +81 -0
- package/demo/dbmr.schema.json +338 -0
- package/demo/middleware/authenticate.js +14 -0
- package/demo/middleware/hasPermission.js +30 -0
- package/demo/middleware/logger.js +67 -0
- package/demo/middleware/tenantIsolation.js +17 -0
- package/demo/migrations/20260509170349_create_migrations_table.sql +6 -0
- package/demo/migrations/20260509170349_create_saas_tables.sql +69 -0
- package/demo/migrations/20260509170349_create_tables.sql +193 -0
- package/demo/models/addresses.js +24 -0
- package/demo/models/cart_items.js +20 -0
- package/demo/models/carts.js +18 -0
- package/demo/models/categories.js +22 -0
- package/demo/models/coupons.js +25 -0
- package/demo/models/index.js +43 -0
- package/demo/models/order_items.js +23 -0
- package/demo/models/orders.js +27 -0
- package/demo/models/payments.js +23 -0
- package/demo/models/product_images.js +20 -0
- package/demo/models/product_reviews.js +22 -0
- package/demo/models/product_variants.js +22 -0
- package/demo/models/products.js +32 -0
- package/demo/models/role_permissions.js +17 -0
- package/demo/models/roles.js +17 -0
- package/demo/models/shipments.js +21 -0
- package/demo/models/tenants.js +18 -0
- package/demo/models/users.js +23 -0
- package/demo/models/webhook_logs.js +22 -0
- package/demo/models/webhooks.js +19 -0
- package/demo/models/wishlists.js +17 -0
- package/demo/openapi.json +7000 -0
- package/demo/package-lock.json +2810 -0
- package/demo/package.json +43 -0
- package/demo/routes/addresses/index.js +6 -0
- package/demo/routes/auth/index.js +55 -0
- package/demo/routes/carts/cart_items/index.js +7 -0
- package/demo/routes/carts/index.js +6 -0
- package/demo/routes/categories/index.js +6 -0
- package/demo/routes/coupons/index.js +6 -0
- package/demo/routes/docs.js +18 -0
- package/demo/routes/health.js +35 -0
- package/demo/routes/index.js +54 -0
- package/demo/routes/orders/index.js +6 -0
- package/demo/routes/orders/order_items/index.js +7 -0
- package/demo/routes/orders/payments/index.js +7 -0
- package/demo/routes/orders/shipments/index.js +7 -0
- package/demo/routes/products/index.js +6 -0
- package/demo/routes/products/product_images/index.js +7 -0
- package/demo/routes/products/product_reviews/index.js +7 -0
- package/demo/routes/products/product_variants/index.js +7 -0
- package/demo/routes/roles/index.js +75 -0
- package/demo/routes/roles/permissions/index.js +47 -0
- package/demo/routes/tenants/index.js +45 -0
- package/demo/routes/users/index.js +45 -0
- package/demo/routes/wishlists/index.js +6 -0
- package/demo/seeds/saas-seed.js +329 -0
- package/docker-compose.yml +61 -0
- package/package.json +120 -113
- package/scripts/demo-create.js +1 -1
- package/skill/SKILL.md +119 -3
- package/src/cli/commands/db-manager.js +134 -0
- package/src/cli/commands/generate.js +106 -60
- package/src/cli/commands/help.js +0 -1
- package/src/cli/generate-route.js +60 -21
- package/src/cli/generate-saas-structure.js +122 -0
- package/src/cli/init/generators.js +6 -0
- package/src/cli/init.js +8 -0
- package/src/cli/main.js +8 -1
- package/src/cli/saas/generate-saas-middleware.js +108 -0
- package/src/cli/saas/generate-saas-migrations.js +480 -0
- package/src/cli/saas/generate-saas-models.js +211 -0
- package/src/cli/saas/generate-saas-openapi.js +419 -0
- package/src/cli/saas/generate-saas-routes.js +435 -0
- package/src/cli/saas/generate-saas-seeds.js +243 -0
- package/src/cli/saas/generate-saas-utils.js +176 -0
- package/src/commons/kafka.js +139 -0
- package/src/commons/model.js +29 -9
- package/src/index.js +2 -0
- package/src/mssql/db.js +41 -3
- package/src/mysql/db.js +3 -0
- package/src/postgres/db.js +6 -0
- package/src/cli/generate-db-manager.js +0 -1573
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
const { generateSaasMigrations } = require("./saas/generate-saas-migrations");
|
|
7
|
+
const { generateSaasModels } = require("./saas/generate-saas-models");
|
|
8
|
+
const {
|
|
9
|
+
generateAuthenticateMiddleware,
|
|
10
|
+
generateTenantIsolationMiddleware,
|
|
11
|
+
generateHasPermissionMiddleware,
|
|
12
|
+
} = require("./saas/generate-saas-middleware");
|
|
13
|
+
const {
|
|
14
|
+
generateCrudRoutes,
|
|
15
|
+
generateAuthRoutes,
|
|
16
|
+
generateRoutesIndex,
|
|
17
|
+
} = require("./saas/generate-saas-routes");
|
|
18
|
+
const { generateSaasSeeds } = require("./saas/generate-saas-seeds");
|
|
19
|
+
const {
|
|
20
|
+
generatePasswordUtil,
|
|
21
|
+
generateModulesUtil,
|
|
22
|
+
generateWebhookUtil,
|
|
23
|
+
} = require("./saas/generate-saas-utils");
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Read the existing .gitignore file and append `credentials.md` if not already present.
|
|
27
|
+
* Returns the full .gitignore content to be written.
|
|
28
|
+
*
|
|
29
|
+
* @returns {string} Updated .gitignore content
|
|
30
|
+
*/
|
|
31
|
+
function getGitignoreContent() {
|
|
32
|
+
const gitignorePath = path.join(process.cwd(), ".gitignore");
|
|
33
|
+
let content = "";
|
|
34
|
+
if (fs.existsSync(gitignorePath)) {
|
|
35
|
+
content = fs.readFileSync(gitignorePath, "utf8");
|
|
36
|
+
}
|
|
37
|
+
if (!content.includes("credentials.md")) {
|
|
38
|
+
content = content.trimEnd() + "\ncredentials.md\n";
|
|
39
|
+
}
|
|
40
|
+
return content;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Main SaaS structure generator orchestrator.
|
|
45
|
+
*
|
|
46
|
+
* Calls all sub-generators and aggregates their output into a single
|
|
47
|
+
* planned[] array of { relPath, content } objects compatible with the
|
|
48
|
+
* existing generate command's file write loop.
|
|
49
|
+
*
|
|
50
|
+
* @param {string} adapter - Database adapter name (e.g. "postgres", "mysql", "sqlite3")
|
|
51
|
+
* @param {object} [options] - Generation options
|
|
52
|
+
* @param {boolean} [options.dryRun] - If true, files will not be written (handled by caller)
|
|
53
|
+
* @param {boolean} [options.json] - If true, output JSON format (handled by caller)
|
|
54
|
+
* @param {Date|number} [options.timestamp] - Base timestamp for migration files
|
|
55
|
+
* @param {string[]} [options.tableNames] - Schema-generated table names for routes index
|
|
56
|
+
* @param {Array<{parent, child, foreignKey}>} [options.relationships] - Schema relationships for routes index
|
|
57
|
+
* @param {{ includeDocs?: boolean }} [options.routeOptions] - Options for routes index generation
|
|
58
|
+
* @returns {Array<{ relPath: string, content: string }>} Combined planned file array
|
|
59
|
+
*/
|
|
60
|
+
function generateSaasStructure(adapter, options) {
|
|
61
|
+
const opts = options || {};
|
|
62
|
+
const planned = [];
|
|
63
|
+
|
|
64
|
+
// 1. Migrations
|
|
65
|
+
const timestamp = opts.timestamp || new Date();
|
|
66
|
+
const migrations = generateSaasMigrations(adapter, timestamp);
|
|
67
|
+
for (const entry of migrations) {
|
|
68
|
+
planned.push(entry);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 2. Models (includes models/index.js barrel with both SaaS + dbmr tables)
|
|
72
|
+
const models = generateSaasModels(adapter, opts.tableNames || []);
|
|
73
|
+
for (const entry of models) {
|
|
74
|
+
planned.push(entry);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 3. Middleware
|
|
78
|
+
planned.push(generateAuthenticateMiddleware());
|
|
79
|
+
planned.push(generateTenantIsolationMiddleware());
|
|
80
|
+
planned.push(generateHasPermissionMiddleware());
|
|
81
|
+
|
|
82
|
+
// 4. Routes (CRUD + auth + combined index with dbmr routes)
|
|
83
|
+
const crudRoutes = generateCrudRoutes();
|
|
84
|
+
for (const entry of crudRoutes) {
|
|
85
|
+
planned.push(entry);
|
|
86
|
+
}
|
|
87
|
+
planned.push(generateAuthRoutes());
|
|
88
|
+
planned.push(
|
|
89
|
+
generateRoutesIndex(
|
|
90
|
+
opts.tableNames || [],
|
|
91
|
+
opts.relationships || [],
|
|
92
|
+
opts.routeOptions || {},
|
|
93
|
+
),
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
// 5. Seeds
|
|
97
|
+
const seeds = generateSaasSeeds(adapter);
|
|
98
|
+
for (const entry of seeds) {
|
|
99
|
+
planned.push(entry);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 6. Utilities
|
|
103
|
+
planned.push({
|
|
104
|
+
relPath: "commons/password.js",
|
|
105
|
+
content: generatePasswordUtil(),
|
|
106
|
+
});
|
|
107
|
+
planned.push({
|
|
108
|
+
relPath: "commons/modules.js",
|
|
109
|
+
content: generateModulesUtil(),
|
|
110
|
+
});
|
|
111
|
+
planned.push({
|
|
112
|
+
relPath: "commons/webhook.js",
|
|
113
|
+
content: generateWebhookUtil(),
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// 7. .gitignore update (add credentials.md)
|
|
117
|
+
planned.push({ relPath: ".gitignore", content: getGitignoreContent() });
|
|
118
|
+
|
|
119
|
+
return planned;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
module.exports = { generateSaasStructure, getGitignoreContent };
|
|
@@ -169,6 +169,7 @@ function buildEnvContent(answers, mode, secrets) {
|
|
|
169
169
|
const lines = [];
|
|
170
170
|
lines.push("# Server");
|
|
171
171
|
lines.push("PORT=3000");
|
|
172
|
+
lines.push("API_BASE_PATH=/api");
|
|
172
173
|
lines.push("");
|
|
173
174
|
lines.push("# Database");
|
|
174
175
|
|
|
@@ -411,6 +412,10 @@ app.use(express.urlencoded({ extended: true }));
|
|
|
411
412
|
${helmetBlock ? helmetBlock + "\n" : ""}${rateLimitBlock ? rateLimitBlock + "\n" : ""}${sessionBlock(answers)}
|
|
412
413
|
app.use(logger);
|
|
413
414
|
|
|
415
|
+
// Routes
|
|
416
|
+
import routes from "#routes/index.js";
|
|
417
|
+
app.use(process.env.API_BASE_PATH || "/api", routes);
|
|
418
|
+
|
|
414
419
|
// Health check
|
|
415
420
|
app.get("/health", (req, res) => {
|
|
416
421
|
res.json({ status: "ok", timestamp: new Date().toISOString() });
|
|
@@ -1732,6 +1737,7 @@ import applySecurity from "${commonsPrefix}/security.js";
|
|
|
1732
1737
|
import logger from "${middlewarePrefix}/logger.js";
|
|
1733
1738
|
import route from "${routePrefix}/index.js";
|
|
1734
1739
|
import { fileURLToPath } from 'node:url';
|
|
1740
|
+
import path from "path";
|
|
1735
1741
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
1736
1742
|
const app = express();
|
|
1737
1743
|
const PORT = process.env.PORT || 3000;
|
package/src/cli/init.js
CHANGED
|
@@ -254,6 +254,14 @@ function updatePackageJson(answers, outputDir) {
|
|
|
254
254
|
const scripts = getScripts(outputDir);
|
|
255
255
|
|
|
256
256
|
pkg.type = "module";
|
|
257
|
+
pkg.imports = {
|
|
258
|
+
"#root/*.js": "./*.js",
|
|
259
|
+
"#models": "./models/index.js",
|
|
260
|
+
"#models/*.js": "./models/*.js",
|
|
261
|
+
"#routes/*.js": "./routes/*.js",
|
|
262
|
+
"#commons/*.js": "./commons/*.js",
|
|
263
|
+
"#middleware/*.js": "./middleware/*.js",
|
|
264
|
+
};
|
|
257
265
|
pkg.scripts = Object.assign({}, pkg.scripts || {}, scripts);
|
|
258
266
|
pkg.dependencies = Object.assign({}, pkg.dependencies || {}, dependencies);
|
|
259
267
|
pkg.devDependencies = Object.assign(
|
package/src/cli/main.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
const { parseFlags, OutputContext } = require("./flags");
|
|
@@ -9,6 +9,7 @@ const generateCmd = require("./commands/generate");
|
|
|
9
9
|
const doctorCmd = require("./commands/doctor");
|
|
10
10
|
const diffCmd = require("./commands/diff");
|
|
11
11
|
const helpCmd = require("./commands/help");
|
|
12
|
+
const dbManagerCmd = require("./commands/db-manager");
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Map of subcommand names to their handler functions.
|
|
@@ -19,6 +20,7 @@ const COMMANDS = {
|
|
|
19
20
|
generate: generateCmd,
|
|
20
21
|
doctor: doctorCmd,
|
|
21
22
|
diff: diffCmd,
|
|
23
|
+
"db-manager": dbManagerCmd,
|
|
22
24
|
help: helpCmd,
|
|
23
25
|
};
|
|
24
26
|
|
|
@@ -31,6 +33,7 @@ const COMMAND_DESCRIPTIONS = {
|
|
|
31
33
|
generate: "Generate models, routes, tests, and OpenAPI spec from a schema",
|
|
32
34
|
doctor: "Validate schema, check dependencies, and verify file sync",
|
|
33
35
|
diff: "Preview changes between current files and what the schema would produce",
|
|
36
|
+
"db-manager": "Start a live database management UI",
|
|
34
37
|
help: "Show help for a command",
|
|
35
38
|
};
|
|
36
39
|
|
|
@@ -68,6 +71,10 @@ const COMMAND_FLAGS = {
|
|
|
68
71
|
],
|
|
69
72
|
doctor: [["--from <path>", "Schema file (default: dbmr.schema.json)"]],
|
|
70
73
|
diff: [["--from <path>", "Schema file (default: dbmr.schema.json)"]],
|
|
74
|
+
"db-manager": [
|
|
75
|
+
["--env <path>", "Path to .env file (default: .env)"],
|
|
76
|
+
["--port <number>", "Server port (default: 4000)"],
|
|
77
|
+
],
|
|
71
78
|
};
|
|
72
79
|
|
|
73
80
|
/**
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SaaS middleware generators.
|
|
5
|
+
*
|
|
6
|
+
* Each function returns a { relPath, content } object for a middleware file.
|
|
7
|
+
* Generated code uses ES6 module syntax (import/export).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Generate the authenticate middleware file.
|
|
12
|
+
*
|
|
13
|
+
* @returns {{ relPath: string, content: string }}
|
|
14
|
+
*/
|
|
15
|
+
function generateAuthenticateMiddleware() {
|
|
16
|
+
const relPath = "middleware/authenticate.js";
|
|
17
|
+
const content = `/**
|
|
18
|
+
* Authentication middleware.
|
|
19
|
+
*
|
|
20
|
+
* Validates that the request has an active session with a user object.
|
|
21
|
+
* Responds with 401 Unauthorized if no valid session exists.
|
|
22
|
+
*/
|
|
23
|
+
function authenticate(req, res, next) {
|
|
24
|
+
if (!req.session || !req.session.user) {
|
|
25
|
+
return res.status(401).json({ message: "Unauthorized" });
|
|
26
|
+
}
|
|
27
|
+
next();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default authenticate;
|
|
31
|
+
`;
|
|
32
|
+
return { relPath, content };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Generate the tenant isolation middleware file.
|
|
37
|
+
*
|
|
38
|
+
* @returns {{ relPath: string, content: string }}
|
|
39
|
+
*/
|
|
40
|
+
function generateTenantIsolationMiddleware() {
|
|
41
|
+
const relPath = "middleware/tenantIsolation.js";
|
|
42
|
+
const content = `/**
|
|
43
|
+
* Tenant isolation middleware.
|
|
44
|
+
*
|
|
45
|
+
* Restricts data access to the user's own tenant unless the user
|
|
46
|
+
* has a global-scoped permission. Injects tenant_id into query
|
|
47
|
+
* and body parameters for non-global users.
|
|
48
|
+
*/
|
|
49
|
+
function tenantIsolation(req, res, next) {
|
|
50
|
+
const hasGlobal = req.session.permission.some((p) => p.scope === "global");
|
|
51
|
+
if (!hasGlobal) {
|
|
52
|
+
req.query.tenant_id = req.session.user.tenant_id;
|
|
53
|
+
req.body.tenant_id = req.session.user.tenant_id;
|
|
54
|
+
}
|
|
55
|
+
next();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export default tenantIsolation;
|
|
59
|
+
`;
|
|
60
|
+
return { relPath, content };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Generate the hasPermission middleware file.
|
|
65
|
+
*
|
|
66
|
+
* @returns {{ relPath: string, content: string }}
|
|
67
|
+
*/
|
|
68
|
+
function generateHasPermissionMiddleware() {
|
|
69
|
+
const relPath = "middleware/hasPermission.js";
|
|
70
|
+
const content = `import { isValidModule } from "#commons/modules.js";
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Permission validation middleware factory.
|
|
74
|
+
*
|
|
75
|
+
* Returns a middleware function that checks whether the authenticated user
|
|
76
|
+
* has the required permission for the specified module and action.
|
|
77
|
+
* A permission entry with action "global" grants access to any action
|
|
78
|
+
* on that module.
|
|
79
|
+
*
|
|
80
|
+
* @param {string} module - The module name to check permission for
|
|
81
|
+
* @param {string} action - The required action
|
|
82
|
+
* @returns {function} Express middleware function
|
|
83
|
+
*/
|
|
84
|
+
function hasPermission(module, action) {
|
|
85
|
+
return (req, res, next) => {
|
|
86
|
+
if (!isValidModule(module)) {
|
|
87
|
+
return res.status(403).json({ message: "Invalid module" });
|
|
88
|
+
}
|
|
89
|
+
const match = req.session.permission.find(
|
|
90
|
+
(p) => p.module === module && (p.action === action || p.action === "global")
|
|
91
|
+
);
|
|
92
|
+
if (!match) {
|
|
93
|
+
return res.status(403).json({ message: "Forbidden" });
|
|
94
|
+
}
|
|
95
|
+
next();
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export default hasPermission;
|
|
100
|
+
`;
|
|
101
|
+
return { relPath, content };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = {
|
|
105
|
+
generateAuthenticateMiddleware,
|
|
106
|
+
generateTenantIsolationMiddleware,
|
|
107
|
+
generateHasPermissionMiddleware,
|
|
108
|
+
};
|