db-model-router 1.0.9 → 1.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/.codegraph/config.json +143 -0
  2. package/CLAUDE.md +150 -0
  3. package/dbmr.schema.json +3 -0
  4. package/docs/dbmr-schema-spec.md +597 -393
  5. package/package.json +1 -1
  6. package/skill/SKILL.md +1 -1
  7. package/skill/references/dbmr-schema-spec.md +597 -0
  8. package/src/cli/commands/generate.js +6 -6
  9. package/src/cli/diff-engine.js +4 -4
  10. package/src/cli/generate-migration.js +4 -0
  11. package/src/cli/generate-route.js +3 -3
  12. package/src/schema/schema-validator.js +1 -1
  13. package/.env +0 -7
  14. package/db-manager/.dbmanager.sqlite +0 -0
  15. package/db-manager/.dbmanager.sqlite-shm +0 -0
  16. package/db-manager/.dbmanager.sqlite-wal +0 -0
  17. package/db-manager/demo/cockroachdb.env +0 -6
  18. package/db-manager/demo/demo.sqlite +0 -0
  19. package/db-manager/demo/dynamodb.env +0 -7
  20. package/db-manager/demo/mongodb.env +0 -4
  21. package/db-manager/demo/mssql.env +0 -6
  22. package/db-manager/demo/mysql.env +0 -6
  23. package/db-manager/demo/oracle.env +0 -6
  24. package/db-manager/demo/postgres.env +0 -6
  25. package/db-manager/demo/redis.env +0 -4
  26. package/db-manager/demo/seeds/cockroachdb.sql +0 -32
  27. package/db-manager/demo/seeds/mssql.sql +0 -32
  28. package/db-manager/demo/seeds/mysql.sql +0 -32
  29. package/db-manager/demo/seeds/oracle.sql +0 -43
  30. package/db-manager/demo/seeds/postgres.sql +0 -32
  31. package/db-manager/demo/seeds/sqlite3.sql +0 -32
  32. package/db-manager/demo/sqlite3.env +0 -2
  33. package/demo/.dockerignore +0 -7
  34. package/demo/.env.example +0 -15
  35. package/demo/Dockerfile +0 -20
  36. package/demo/app.js +0 -39
  37. package/demo/commons/add_migration.js +0 -43
  38. package/demo/commons/db.js +0 -17
  39. package/demo/commons/migrate.js +0 -68
  40. package/demo/commons/modules.js +0 -18
  41. package/demo/commons/password.js +0 -36
  42. package/demo/commons/security.js +0 -30
  43. package/demo/commons/session.js +0 -13
  44. package/demo/commons/webhook.js +0 -81
  45. package/demo/dbmr.schema.json +0 -338
  46. package/demo/middleware/authenticate.js +0 -14
  47. package/demo/middleware/hasPermission.js +0 -30
  48. package/demo/middleware/logger.js +0 -67
  49. package/demo/middleware/tenantIsolation.js +0 -19
  50. package/demo/migrations/20260510193736_create_migrations_table.sql +0 -6
  51. package/demo/migrations/20260510193737_create_saas_tables.sql +0 -69
  52. package/demo/migrations/20260510193737_create_tables.sql +0 -193
  53. package/demo/models/addresses.js +0 -24
  54. package/demo/models/cart_items.js +0 -20
  55. package/demo/models/carts.js +0 -18
  56. package/demo/models/categories.js +0 -22
  57. package/demo/models/coupons.js +0 -25
  58. package/demo/models/index.js +0 -43
  59. package/demo/models/order_items.js +0 -23
  60. package/demo/models/orders.js +0 -27
  61. package/demo/models/payments.js +0 -23
  62. package/demo/models/product_images.js +0 -20
  63. package/demo/models/product_reviews.js +0 -22
  64. package/demo/models/product_variants.js +0 -22
  65. package/demo/models/products.js +0 -32
  66. package/demo/models/role_permissions.js +0 -17
  67. package/demo/models/roles.js +0 -17
  68. package/demo/models/shipments.js +0 -21
  69. package/demo/models/tenants.js +0 -18
  70. package/demo/models/users.js +0 -23
  71. package/demo/models/webhook_logs.js +0 -22
  72. package/demo/models/webhooks.js +0 -19
  73. package/demo/models/wishlists.js +0 -17
  74. package/demo/openapi.json +0 -7000
  75. package/demo/package-lock.json +0 -3972
  76. package/demo/package.json +0 -46
  77. package/demo/routes/addresses/index.js +0 -10
  78. package/demo/routes/auth/index.js +0 -55
  79. package/demo/routes/carts/cart_items/index.js +0 -11
  80. package/demo/routes/carts/index.js +0 -14
  81. package/demo/routes/categories/index.js +0 -10
  82. package/demo/routes/coupons/index.js +0 -10
  83. package/demo/routes/docs.js +0 -18
  84. package/demo/routes/health.js +0 -35
  85. package/demo/routes/index.js +0 -40
  86. package/demo/routes/orders/index.js +0 -18
  87. package/demo/routes/orders/order_items/index.js +0 -11
  88. package/demo/routes/orders/payments/index.js +0 -11
  89. package/demo/routes/orders/shipments/index.js +0 -11
  90. package/demo/routes/products/index.js +0 -18
  91. package/demo/routes/products/product_images/index.js +0 -11
  92. package/demo/routes/products/product_reviews/index.js +0 -11
  93. package/demo/routes/products/product_variants/index.js +0 -11
  94. package/demo/routes/roles/index.js +0 -75
  95. package/demo/routes/roles/permissions/index.js +0 -47
  96. package/demo/routes/tenants/index.js +0 -45
  97. package/demo/routes/users/index.js +0 -45
  98. package/demo/routes/wishlists/index.js +0 -10
  99. package/demo/seeds/saas-seed.js +0 -329
@@ -317,6 +317,10 @@ function generateCreateTableSQL(tableName, tableDef, adapter) {
317
317
  const pk = tableDef.pk;
318
318
 
319
319
  for (const [colName, rule] of Object.entries(tableDef.columns)) {
320
+ // Skip dot-notation keys — they are object sub-key validation rules,
321
+ // not standalone database columns. Only the parent object column is created.
322
+ if (colName.includes(".")) continue;
323
+
320
324
  const { sqlType, nullable, isAutoIncrement } = mapColumnType(rule, adapter);
321
325
  let line = ` ${quoteIdent(colName, adapter)} ${sqlType}`;
322
326
 
@@ -40,7 +40,7 @@ export default router;
40
40
 
41
41
  /**
42
42
  * Generate a parent route file that includes its own CRUD and mounts child routes.
43
- * e.g., routes/users.js mounts posts under /:user_id/posts
43
+ * e.g., routes/orders/index.js mounts order_items under /:order_id/items
44
44
  *
45
45
  * @param {string} tableName - Parent table name
46
46
  * @param {Array<{child, foreignKey}>} children - Child relationships for this parent
@@ -56,7 +56,7 @@ import { ${varName} } from "#models";
56
56
  // Import child routes
57
57
  for (const child of children) {
58
58
  const childVar = safeVarName(child.child);
59
- code += `import ${childVar}Route from "./${tableName}/${child.child}.js";\n`;
59
+ code += `import ${childVar}Route from "./${child.child}/index.js";\n`;
60
60
  }
61
61
 
62
62
  code += `
@@ -129,7 +129,7 @@ function generateRoutesIndexFile(tableNames, relationships = [], options = {}) {
129
129
  for (const table of tableNames) {
130
130
  if (nestedChildren.has(table)) continue;
131
131
  const varName = safeVarName(table);
132
- imports += `import ${varName}Route from "./${table}.js";\n`;
132
+ imports += `import ${varName}Route from "./${table}/index.js";\n`;
133
133
  }
134
134
 
135
135
  // Import docs route if openapi is generated
@@ -16,7 +16,7 @@ const VALID_ADAPTERS = new Set([
16
16
  const VALID_FRAMEWORKS = new Set(["express", "ultimate-express"]);
17
17
 
18
18
  const COLUMN_RULE_RE =
19
- /^(required\|)?(string|integer|numeric|boolean|object|datetime|auto_increment)$/;
19
+ /^(required\|)?(string|integer|numeric|boolean|object|datetime|auto_increment)(:[^|]+)?(\|[^|]+)*$/;
20
20
 
21
21
  class SchemaValidationError extends Error {
22
22
  constructor(errors) {
package/.env DELETED
@@ -1,7 +0,0 @@
1
- DB_HOST=3.111.128.240
2
- DB_NAME="partner"
3
- DB_USER="root"
4
- DB_PASS="FN4WyP2jwruMF6"
5
- DB_PORT=3306
6
- PORT=3000
7
- DB_TYPE=mysql
Binary file
Binary file
Binary file
@@ -1,6 +0,0 @@
1
- DB_TYPE=cockroachdb
2
- DB_HOST=localhost
3
- DB_PORT=26257
4
- DB_NAME=defaultdb
5
- DB_USER=root
6
- DB_PASS=
Binary file
@@ -1,7 +0,0 @@
1
- DB_TYPE=dynamodb
2
- DB_HOST=localhost
3
- DB_PORT=8000
4
- DB_NAME=us-east-1
5
- AWS_REGION=us-east-1
6
- AWS_ACCESS_KEY_ID=fakeKey
7
- AWS_SECRET_ACCESS_KEY=fakeSecret
@@ -1,4 +0,0 @@
1
- DB_TYPE=mongodb
2
- DB_HOST=localhost
3
- DB_PORT=27017
4
- DB_NAME=test_db
@@ -1,6 +0,0 @@
1
- DB_TYPE=mssql
2
- DB_HOST=localhost
3
- DB_PORT=1433
4
- DB_NAME=master
5
- DB_USER=sa
6
- DB_PASS=Password123!
@@ -1,6 +0,0 @@
1
- DB_TYPE=mysql
2
- DB_HOST=localhost
3
- DB_PORT=3306
4
- DB_NAME=test_db
5
- DB_USER=root
6
- DB_PASS=password
@@ -1,6 +0,0 @@
1
- DB_TYPE=oracle
2
- DB_HOST=localhost
3
- DB_PORT=1521
4
- DB_NAME=FREEPDB1
5
- DB_USER=system
6
- DB_PASS=oracle
@@ -1,6 +0,0 @@
1
- DB_TYPE=postgres
2
- DB_HOST=localhost
3
- DB_PORT=5432
4
- DB_NAME=test_db
5
- DB_USER=postgres
6
- DB_PASS=password
@@ -1,4 +0,0 @@
1
- DB_TYPE=redis
2
- DB_HOST=localhost
3
- DB_PORT=6379
4
- DB_NAME=0
@@ -1,32 +0,0 @@
1
- -- CockroachDB seed data for DB Manager demo
2
- -- Creates users and products tables with sample data
3
-
4
- DROP TABLE IF EXISTS users;
5
- DROP TABLE IF EXISTS products;
6
-
7
- CREATE TABLE users (
8
- id SERIAL PRIMARY KEY,
9
- name VARCHAR(255) NOT NULL,
10
- email VARCHAR(255) NOT NULL,
11
- created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
12
- );
13
-
14
- CREATE TABLE products (
15
- id SERIAL PRIMARY KEY,
16
- name VARCHAR(255) NOT NULL,
17
- price NUMERIC(10,2) NOT NULL DEFAULT 0,
18
- stock INTEGER NOT NULL DEFAULT 0,
19
- created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
20
- );
21
-
22
- INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com');
23
- INSERT INTO users (name, email) VALUES ('Bob', 'bob@example.com');
24
- INSERT INTO users (name, email) VALUES ('Charlie', 'charlie@example.com');
25
- INSERT INTO users (name, email) VALUES ('Diana', 'diana@example.com');
26
- INSERT INTO users (name, email) VALUES ('Eve', 'eve@example.com');
27
-
28
- INSERT INTO products (name, price, stock) VALUES ('Widget', 9.99, 100);
29
- INSERT INTO products (name, price, stock) VALUES ('Gadget', 24.99, 50);
30
- INSERT INTO products (name, price, stock) VALUES ('Doohickey', 4.99, 200);
31
- INSERT INTO products (name, price, stock) VALUES ('Thingamajig', 49.99, 25);
32
- INSERT INTO products (name, price, stock) VALUES ('Whatchamacallit', 14.99, 75);
@@ -1,32 +0,0 @@
1
- -- MSSQL seed data for DB Manager demo
2
- -- Creates users and products tables with sample data
3
-
4
- IF OBJECT_ID('dbo.users', 'U') IS NOT NULL DROP TABLE dbo.users;
5
- IF OBJECT_ID('dbo.products', 'U') IS NOT NULL DROP TABLE dbo.products;
6
-
7
- CREATE TABLE users (
8
- id INT IDENTITY(1,1) PRIMARY KEY,
9
- name NVARCHAR(255) NOT NULL,
10
- email NVARCHAR(255) NOT NULL,
11
- created_at DATETIME2 NOT NULL DEFAULT GETDATE()
12
- );
13
-
14
- CREATE TABLE products (
15
- id INT IDENTITY(1,1) PRIMARY KEY,
16
- name NVARCHAR(255) NOT NULL,
17
- price DECIMAL(10,2) NOT NULL DEFAULT 0,
18
- stock INT NOT NULL DEFAULT 0,
19
- created_at DATETIME2 NOT NULL DEFAULT GETDATE()
20
- );
21
-
22
- INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com');
23
- INSERT INTO users (name, email) VALUES ('Bob', 'bob@example.com');
24
- INSERT INTO users (name, email) VALUES ('Charlie', 'charlie@example.com');
25
- INSERT INTO users (name, email) VALUES ('Diana', 'diana@example.com');
26
- INSERT INTO users (name, email) VALUES ('Eve', 'eve@example.com');
27
-
28
- INSERT INTO products (name, price, stock) VALUES ('Widget', 9.99, 100);
29
- INSERT INTO products (name, price, stock) VALUES ('Gadget', 24.99, 50);
30
- INSERT INTO products (name, price, stock) VALUES ('Doohickey', 4.99, 200);
31
- INSERT INTO products (name, price, stock) VALUES ('Thingamajig', 49.99, 25);
32
- INSERT INTO products (name, price, stock) VALUES ('Whatchamacallit', 14.99, 75);
@@ -1,32 +0,0 @@
1
- -- MySQL seed data for DB Manager demo
2
- -- Creates users and products tables with sample data
3
-
4
- DROP TABLE IF EXISTS users;
5
- DROP TABLE IF EXISTS products;
6
-
7
- CREATE TABLE users (
8
- id INT AUTO_INCREMENT PRIMARY KEY,
9
- name VARCHAR(255) NOT NULL,
10
- email VARCHAR(255) NOT NULL,
11
- created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
12
- );
13
-
14
- CREATE TABLE products (
15
- id INT AUTO_INCREMENT PRIMARY KEY,
16
- name VARCHAR(255) NOT NULL,
17
- price DECIMAL(10,2) NOT NULL DEFAULT 0,
18
- stock INT NOT NULL DEFAULT 0,
19
- created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
20
- );
21
-
22
- INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com');
23
- INSERT INTO users (name, email) VALUES ('Bob', 'bob@example.com');
24
- INSERT INTO users (name, email) VALUES ('Charlie', 'charlie@example.com');
25
- INSERT INTO users (name, email) VALUES ('Diana', 'diana@example.com');
26
- INSERT INTO users (name, email) VALUES ('Eve', 'eve@example.com');
27
-
28
- INSERT INTO products (name, price, stock) VALUES ('Widget', 9.99, 100);
29
- INSERT INTO products (name, price, stock) VALUES ('Gadget', 24.99, 50);
30
- INSERT INTO products (name, price, stock) VALUES ('Doohickey', 4.99, 200);
31
- INSERT INTO products (name, price, stock) VALUES ('Thingamajig', 49.99, 25);
32
- INSERT INTO products (name, price, stock) VALUES ('Whatchamacallit', 14.99, 75);
@@ -1,43 +0,0 @@
1
- -- Oracle seed data for DB Manager demo
2
- -- Creates users and products tables with sample data
3
-
4
- BEGIN
5
- EXECUTE IMMEDIATE 'DROP TABLE users CASCADE CONSTRAINTS';
6
- EXCEPTION
7
- WHEN OTHERS THEN NULL;
8
- END;
9
- /
10
-
11
- BEGIN
12
- EXECUTE IMMEDIATE 'DROP TABLE products CASCADE CONSTRAINTS';
13
- EXCEPTION
14
- WHEN OTHERS THEN NULL;
15
- END;
16
- /
17
-
18
- CREATE TABLE users (
19
- id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
20
- name VARCHAR2(255) NOT NULL,
21
- email VARCHAR2(255) NOT NULL,
22
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
23
- );
24
-
25
- CREATE TABLE products (
26
- id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
27
- name VARCHAR2(255) NOT NULL,
28
- price NUMBER(10,2) DEFAULT 0 NOT NULL,
29
- stock NUMBER DEFAULT 0 NOT NULL,
30
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
31
- );
32
-
33
- INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com');
34
- INSERT INTO users (name, email) VALUES ('Bob', 'bob@example.com');
35
- INSERT INTO users (name, email) VALUES ('Charlie', 'charlie@example.com');
36
- INSERT INTO users (name, email) VALUES ('Diana', 'diana@example.com');
37
- INSERT INTO users (name, email) VALUES ('Eve', 'eve@example.com');
38
-
39
- INSERT INTO products (name, price, stock) VALUES ('Widget', 9.99, 100);
40
- INSERT INTO products (name, price, stock) VALUES ('Gadget', 24.99, 50);
41
- INSERT INTO products (name, price, stock) VALUES ('Doohickey', 4.99, 200);
42
- INSERT INTO products (name, price, stock) VALUES ('Thingamajig', 49.99, 25);
43
- INSERT INTO products (name, price, stock) VALUES ('Whatchamacallit', 14.99, 75);
@@ -1,32 +0,0 @@
1
- -- PostgreSQL seed data for DB Manager demo
2
- -- Creates users and products tables with sample data
3
-
4
- DROP TABLE IF EXISTS users;
5
- DROP TABLE IF EXISTS products;
6
-
7
- CREATE TABLE users (
8
- id SERIAL PRIMARY KEY,
9
- name VARCHAR(255) NOT NULL,
10
- email VARCHAR(255) NOT NULL,
11
- created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
12
- );
13
-
14
- CREATE TABLE products (
15
- id SERIAL PRIMARY KEY,
16
- name VARCHAR(255) NOT NULL,
17
- price NUMERIC(10,2) NOT NULL DEFAULT 0,
18
- stock INTEGER NOT NULL DEFAULT 0,
19
- created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
20
- );
21
-
22
- INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com');
23
- INSERT INTO users (name, email) VALUES ('Bob', 'bob@example.com');
24
- INSERT INTO users (name, email) VALUES ('Charlie', 'charlie@example.com');
25
- INSERT INTO users (name, email) VALUES ('Diana', 'diana@example.com');
26
- INSERT INTO users (name, email) VALUES ('Eve', 'eve@example.com');
27
-
28
- INSERT INTO products (name, price, stock) VALUES ('Widget', 9.99, 100);
29
- INSERT INTO products (name, price, stock) VALUES ('Gadget', 24.99, 50);
30
- INSERT INTO products (name, price, stock) VALUES ('Doohickey', 4.99, 200);
31
- INSERT INTO products (name, price, stock) VALUES ('Thingamajig', 49.99, 25);
32
- INSERT INTO products (name, price, stock) VALUES ('Whatchamacallit', 14.99, 75);
@@ -1,32 +0,0 @@
1
- -- SQLite3 seed data for DB Manager demo
2
- -- Creates users and products tables with sample data
3
-
4
- DROP TABLE IF EXISTS users;
5
- DROP TABLE IF EXISTS products;
6
-
7
- CREATE TABLE users (
8
- id INTEGER PRIMARY KEY AUTOINCREMENT,
9
- name TEXT NOT NULL,
10
- email TEXT NOT NULL,
11
- created_at TEXT NOT NULL DEFAULT (datetime('now'))
12
- );
13
-
14
- CREATE TABLE products (
15
- id INTEGER PRIMARY KEY AUTOINCREMENT,
16
- name TEXT NOT NULL,
17
- price REAL NOT NULL DEFAULT 0,
18
- stock INTEGER NOT NULL DEFAULT 0,
19
- created_at TEXT NOT NULL DEFAULT (datetime('now'))
20
- );
21
-
22
- INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com');
23
- INSERT INTO users (name, email) VALUES ('Bob', 'bob@example.com');
24
- INSERT INTO users (name, email) VALUES ('Charlie', 'charlie@example.com');
25
- INSERT INTO users (name, email) VALUES ('Diana', 'diana@example.com');
26
- INSERT INTO users (name, email) VALUES ('Eve', 'eve@example.com');
27
-
28
- INSERT INTO products (name, price, stock) VALUES ('Widget', 9.99, 100);
29
- INSERT INTO products (name, price, stock) VALUES ('Gadget', 24.99, 50);
30
- INSERT INTO products (name, price, stock) VALUES ('Doohickey', 4.99, 200);
31
- INSERT INTO products (name, price, stock) VALUES ('Thingamajig', 49.99, 25);
32
- INSERT INTO products (name, price, stock) VALUES ('Whatchamacallit', 14.99, 75);
@@ -1,2 +0,0 @@
1
- DB_TYPE=sqlite3
2
- DB_NAME=./db-manager/demo/demo.sqlite
@@ -1,7 +0,0 @@
1
- node_modules
2
- npm-debug.log
3
- .env
4
- .env.example
5
- .git
6
- .gitignore
7
- data
package/demo/.env.example DELETED
@@ -1,15 +0,0 @@
1
- # Server
2
- PORT=3000
3
- API_BASE_PATH=/api
4
-
5
- # Database
6
- DB_TYPE=sqlite3
7
- DB_NAME=./data/data.db
8
-
9
- # Session
10
- SESSION_SECRET=your_session_secret
11
-
12
- # Logging
13
- APP_NAME=your_app_name
14
- LOG_LEVEL=info
15
- LOKI_HOST=http://your-loki-host:3100
package/demo/Dockerfile DELETED
@@ -1,20 +0,0 @@
1
- FROM node:alpine
2
-
3
- WORKDIR /app
4
-
5
- # Install dependencies
6
- COPY package*.json ./
7
- RUN npm ci --omit=dev
8
-
9
- # Copy application files
10
- COPY app.js ./
11
- COPY commons/ ./commons/
12
- COPY middleware/ ./middleware/
13
- COPY route/ ./route/
14
- COPY migrations/ ./migrations/
15
-
16
- # Expose port
17
- EXPOSE 3000
18
-
19
- # Start the application
20
- CMD ["node", "app.js"]
package/demo/app.js DELETED
@@ -1,39 +0,0 @@
1
- import express from "express";
2
- import "./commons/db.js";
3
- import configureSession from "./commons/session.js";
4
- import applySecurity from "./commons/security.js";
5
- import logger from "./middleware/logger.js";
6
- import routes from "./routes/index.js";
7
- import { fileURLToPath } from 'node:url';
8
- import path from "path";
9
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
- const app = express();
11
- const PORT = process.env.PORT || 3000;
12
-
13
- // Middleware
14
- app.use(express.json());
15
- app.use(express.urlencoded({ extended: true }));
16
-
17
- // Security (helmet, rate limiting, custom headers)
18
- applySecurity(app);
19
-
20
- // Session
21
- app.use(configureSession());
22
-
23
- // Logger
24
- app.use(logger);
25
-
26
- // Routes
27
- app.use(process.env.API_BASE_PATH || "/api", routes);
28
-
29
- // Error handler
30
- app.use((err, req, res, next) => {
31
- console.error(err.stack);
32
- res.status(500).json({ type: "danger", message: "Internal Server Error" });
33
- });
34
-
35
- app.listen(PORT, () => {
36
- console.log(`Server running on port ${PORT}`);
37
- });
38
-
39
- export default app;
@@ -1,43 +0,0 @@
1
- #!/usr/bin/env node
2
- import fs from "fs";
3
- import path from "path";
4
- import { fileURLToPath } from "url";
5
-
6
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
-
8
- /**
9
- * Create a new timestamped migration file.
10
- * @param {string} migrationsDir - absolute path to migrations folder
11
- * @param {string} [name] - migration name (default: "migration")
12
- * @returns {string} the created filename
13
- */
14
- export default function addMigration(migrationsDir, name) {
15
- const migrationName = name || "migration";
16
- const now = new Date();
17
- const y = String(now.getFullYear()).padStart(4, "0");
18
- const mo = String(now.getMonth() + 1).padStart(2, "0");
19
- const d = String(now.getDate()).padStart(2, "0");
20
- const h = String(now.getHours()).padStart(2, "0");
21
- const mi = String(now.getMinutes()).padStart(2, "0");
22
- const s = String(now.getSeconds()).padStart(2, "0");
23
- const ts = `${y}${mo}${d}${h}${mi}${s}`;
24
-
25
- const filename = `${ts}_${migrationName}.sql`;
26
- const filePath = path.join(migrationsDir, filename);
27
-
28
- if (!fs.existsSync(migrationsDir)) {
29
- fs.mkdirSync(migrationsDir, { recursive: true });
30
- }
31
-
32
- fs.writeFileSync(filePath, "-- Write your migration SQL here\n");
33
- console.log(`Created migration: ${filename}`);
34
- return filename;
35
- }
36
-
37
- // Run as standalone script
38
- const isMain = process.argv[1] && fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url));
39
- if (isMain) {
40
- const migrationsDir = path.join(__dirname, "../migrations");
41
- const name = process.argv[2] || "migration";
42
- addMigration(migrationsDir, name);
43
- }
@@ -1,17 +0,0 @@
1
- import "dotenv/config";
2
- import dbModelRouter from "db-model-router";
3
-
4
- // Initialize database adapter
5
- dbModelRouter.init("sqlite3");
6
-
7
- // Connect to database
8
- dbModelRouter.db.connect({
9
- database: process.env.DB_NAME || "./data/data.db",
10
- });
11
-
12
- // Make db available globally across the application
13
- const db = dbModelRouter.db;
14
- global.db = db;
15
-
16
- export { db };
17
- export default db;
@@ -1,68 +0,0 @@
1
- #!/usr/bin/env node
2
- import fs from "fs";
3
- import path from "path";
4
- import crypto from "crypto";
5
- import { fileURLToPath } from "url";
6
-
7
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
-
9
- /**
10
- * Run all pending SQL migrations from the migrations directory.
11
- * @param {object} db - db-model-router db instance
12
- * @param {string} migrationsDir - absolute path to migrations folder
13
- */
14
- export default async function runMigrations(db, migrationsDir) {
15
- const files = fs.readdirSync(migrationsDir)
16
- .filter(f => f.endsWith(".sql"))
17
- .sort();
18
-
19
- let executed;
20
- try {
21
- const result = await db.query("SELECT filename FROM _migrations");
22
- executed = new Set((result || []).map(r => r.filename));
23
- } catch (e) {
24
- executed = new Set();
25
- }
26
-
27
- let ran = 0;
28
- for (const file of files) {
29
- if (executed.has(file)) {
30
- console.log(` Skipping (already executed): ${file}`);
31
- continue;
32
- }
33
- const filePath = path.join(migrationsDir, file);
34
- const content = fs.readFileSync(filePath, "utf8");
35
- const checksum = crypto.createHash("md5").update(content).digest("hex");
36
-
37
- console.log(` Running migration: ${file}`);
38
- await db.query(content);
39
- await db.query(
40
- "INSERT INTO _migrations (filename, checksum) VALUES (?, ?)",
41
- [file, checksum]
42
- );
43
- console.log(` Completed: ${file}`);
44
- ran++;
45
- }
46
-
47
- if (ran === 0) {
48
- console.log("No pending migrations.");
49
- } else {
50
- console.log(`\n${ran} migration(s) complete.`);
51
- }
52
- }
53
-
54
- // Run as standalone script
55
- const isMain = process.argv[1] && fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url));
56
- if (isMain) {
57
- await import("dotenv/config");
58
- const pkg = await import("db-model-router");
59
- const mod = pkg.default || pkg;
60
- mod.init("sqlite3");
61
- mod.db.connect({
62
- database: process.env.DB_NAME || "./data/data.db",
63
- });
64
- const migrationsDir = path.join(__dirname, "../migrations");
65
- runMigrations(mod.db, migrationsDir)
66
- .then(() => process.exit(0))
67
- .catch(err => { console.error("Migration failed:", err); process.exit(1); });
68
- }
@@ -1,18 +0,0 @@
1
- /**
2
- * Registry of all SaaS module names.
3
- * This is the single source of truth for valid module identifiers
4
- * used by the permission system.
5
- *
6
- * @type {string[]}
7
- */
8
- export const modules = ["users", "tenants", "roles", "permissions", "webhooks"];
9
-
10
- /**
11
- * Check whether a given name is a registered module.
12
- *
13
- * @param {string} name - The module name to validate
14
- * @returns {boolean} True if the name exists in the modules registry
15
- */
16
- export function isValidModule(name) {
17
- return modules.includes(name);
18
- }
@@ -1,36 +0,0 @@
1
- import crypto from "crypto";
2
-
3
- /**
4
- * Hash a password using scrypt with a random salt.
5
- * Returns a string in the format "salt:derivedKey" (both hex-encoded).
6
- *
7
- * @param {string} password - The plaintext password to hash
8
- * @returns {Promise<string>} The hashed password string
9
- */
10
- export function hashPassword(password) {
11
- const salt = crypto.randomBytes(16).toString("hex");
12
- return new Promise((resolve, reject) => {
13
- crypto.scrypt(password, salt, 64, (err, derivedKey) => {
14
- if (err) reject(err);
15
- resolve(salt + ":" + derivedKey.toString("hex"));
16
- });
17
- });
18
- }
19
-
20
- /**
21
- * Verify a password against a previously hashed value.
22
- * Uses timing-safe comparison to prevent timing attacks.
23
- *
24
- * @param {string} password - The plaintext password to verify
25
- * @param {string} hash - The stored hash in "salt:derivedKey" format
26
- * @returns {Promise<boolean>} True if the password matches, false otherwise
27
- */
28
- export function verifyPassword(password, hash) {
29
- const [salt, key] = hash.split(":");
30
- return new Promise((resolve, reject) => {
31
- crypto.scrypt(password, salt, 64, (err, derivedKey) => {
32
- if (err) reject(err);
33
- resolve(crypto.timingSafeEqual(Buffer.from(key, "hex"), derivedKey));
34
- });
35
- });
36
- }
@@ -1,30 +0,0 @@
1
- import helmet from "helmet";
2
- import rateLimit from "express-rate-limit";
3
-
4
- /**
5
- * Apply security middleware to the Express app.
6
- * Includes: Helmet, rate limiting, custom security headers.
7
- * @param {import("express").Application} app
8
- */
9
- export default function applySecurity(app) {
10
- // Helmet — sets various HTTP headers for security
11
- app.use(helmet());
12
-
13
- // Rate limiting
14
- app.use(rateLimit({
15
- windowMs: 15 * 60 * 1000,
16
- max: 100,
17
- standardHeaders: true,
18
- legacyHeaders: false,
19
- }));
20
-
21
- // Custom security headers (override or extend as needed)
22
- app.use((req, res, next) => {
23
- res.setHeader("X-Content-Type-Options", "nosniff");
24
- res.setHeader("X-Frame-Options", "DENY");
25
- res.setHeader("X-XSS-Protection", "1; mode=block");
26
- res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
27
- res.removeHeader("X-Powered-By");
28
- next();
29
- });
30
- }
@@ -1,13 +0,0 @@
1
- import session from "express-session";
2
-
3
- /**
4
- * Configure and return session middleware.
5
- * Session store: memory
6
- */
7
- export default function configureSession() {
8
- return session({
9
- secret: process.env.SESSION_SECRET || "change-me",
10
- resave: false,
11
- saveUninitialized: false,
12
- });
13
- }