dbnexus 0.3.1 → 0.4.0

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/cli.js CHANGED
@@ -33113,7 +33113,7 @@ var import_dotenv = __toESM(require_main(), 1);
33113
33113
  import Database from "better-sqlite3";
33114
33114
 
33115
33115
  // packages/metadata/dist/schema.js
33116
- var SCHEMA_VERSION = 19;
33116
+ var SCHEMA_VERSION = 21;
33117
33117
  var MIGRATIONS = [
33118
33118
  // Version 1: Initial schema
33119
33119
  `
@@ -33514,6 +33514,90 @@ var MIGRATIONS = [
33514
33514
  ALTER TABLE servers ADD COLUMN stop_command TEXT;
33515
33515
 
33516
33516
  UPDATE schema_version SET version = 19;
33517
+ `,
33518
+ // Version 20: Add authentication tables (users, api_keys, user_permissions, refresh_tokens)
33519
+ `
33520
+ -- Users table for authentication
33521
+ CREATE TABLE IF NOT EXISTS users (
33522
+ id TEXT PRIMARY KEY,
33523
+ email TEXT UNIQUE NOT NULL,
33524
+ password_hash TEXT NOT NULL,
33525
+ name TEXT,
33526
+ role TEXT NOT NULL DEFAULT 'viewer' CHECK(role IN ('admin', 'editor', 'viewer')),
33527
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
33528
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
33529
+ );
33530
+
33531
+ -- API Keys for CLI/programmatic access
33532
+ CREATE TABLE IF NOT EXISTS api_keys (
33533
+ id TEXT PRIMARY KEY,
33534
+ user_id TEXT NOT NULL,
33535
+ name TEXT NOT NULL,
33536
+ key_hash TEXT NOT NULL,
33537
+ last_used_at TEXT,
33538
+ expires_at TEXT,
33539
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
33540
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
33541
+ );
33542
+
33543
+ -- User permissions for per-database access control
33544
+ CREATE TABLE IF NOT EXISTS user_permissions (
33545
+ id TEXT PRIMARY KEY,
33546
+ user_id TEXT NOT NULL,
33547
+ connection_id TEXT NOT NULL,
33548
+ permission TEXT NOT NULL DEFAULT 'read' CHECK(permission IN ('read', 'write', 'admin')),
33549
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
33550
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
33551
+ FOREIGN KEY (connection_id) REFERENCES connections(id) ON DELETE CASCADE,
33552
+ UNIQUE(user_id, connection_id)
33553
+ );
33554
+
33555
+ -- Refresh tokens for JWT authentication
33556
+ CREATE TABLE IF NOT EXISTS refresh_tokens (
33557
+ id TEXT PRIMARY KEY,
33558
+ user_id TEXT NOT NULL,
33559
+ token_hash TEXT NOT NULL,
33560
+ expires_at TEXT NOT NULL,
33561
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
33562
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
33563
+ );
33564
+
33565
+ CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
33566
+ CREATE INDEX IF NOT EXISTS idx_api_keys_user ON api_keys(user_id);
33567
+ CREATE INDEX IF NOT EXISTS idx_user_permissions_user ON user_permissions(user_id);
33568
+ CREATE INDEX IF NOT EXISTS idx_user_permissions_connection ON user_permissions(connection_id);
33569
+ CREATE INDEX IF NOT EXISTS idx_refresh_tokens_user ON refresh_tokens(user_id);
33570
+ CREATE INDEX IF NOT EXISTS idx_refresh_tokens_expires ON refresh_tokens(expires_at);
33571
+
33572
+ UPDATE schema_version SET version = 20;
33573
+ `,
33574
+ // Version 21: Add created_by column to resources for multi-user ownership
33575
+ `
33576
+ -- Add created_by to connections
33577
+ ALTER TABLE connections ADD COLUMN created_by TEXT REFERENCES users(id) ON DELETE SET NULL;
33578
+ CREATE INDEX IF NOT EXISTS idx_connections_created_by ON connections(created_by);
33579
+
33580
+ -- Add created_by to servers
33581
+ ALTER TABLE servers ADD COLUMN created_by TEXT REFERENCES users(id) ON DELETE SET NULL;
33582
+ CREATE INDEX IF NOT EXISTS idx_servers_created_by ON servers(created_by);
33583
+
33584
+ -- Add created_by to projects
33585
+ ALTER TABLE projects ADD COLUMN created_by TEXT REFERENCES users(id) ON DELETE SET NULL;
33586
+ CREATE INDEX IF NOT EXISTS idx_projects_created_by ON projects(created_by);
33587
+
33588
+ -- Add created_by to database_groups
33589
+ ALTER TABLE database_groups ADD COLUMN created_by TEXT REFERENCES users(id) ON DELETE SET NULL;
33590
+ CREATE INDEX IF NOT EXISTS idx_database_groups_created_by ON database_groups(created_by);
33591
+
33592
+ -- Add created_by to saved_queries
33593
+ ALTER TABLE saved_queries ADD COLUMN created_by TEXT REFERENCES users(id) ON DELETE SET NULL;
33594
+ CREATE INDEX IF NOT EXISTS idx_saved_queries_created_by ON saved_queries(created_by);
33595
+
33596
+ -- Add created_by to backups
33597
+ ALTER TABLE backups ADD COLUMN created_by TEXT REFERENCES users(id) ON DELETE SET NULL;
33598
+ CREATE INDEX IF NOT EXISTS idx_backups_created_by ON backups(created_by);
33599
+
33600
+ UPDATE schema_version SET version = 21;
33517
33601
  `
33518
33602
  ];
33519
33603
 
@@ -33627,15 +33711,15 @@ var ConnectionRepository = class {
33627
33711
  /**
33628
33712
  * Create a new connection with password
33629
33713
  */
33630
- create(input) {
33714
+ create(input, userId) {
33631
33715
  const id = MetadataDatabase.generateId();
33632
33716
  const now = (/* @__PURE__ */ new Date()).toISOString();
33633
33717
  const encryptedPwd = input.password ? encryptPassword(input.password) : null;
33634
33718
  const connectionType = input.connectionType || this.inferConnectionType(input.host);
33635
33719
  this.db.prepare(`
33636
- INSERT INTO connections (id, name, engine, connection_type, host, port, database, username, encrypted_password, ssl, default_schema, tags, read_only, server_id, project_id, group_id, created_at, updated_at)
33637
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
33638
- `).run(id, input.name, input.engine, connectionType, input.host, input.port, input.database, input.username, encryptedPwd, input.ssl ? 1 : 0, input.defaultSchema || null, JSON.stringify(input.tags ?? []), input.readOnly ? 1 : 0, input.serverId || null, input.projectId || null, input.groupId || null, now, now);
33720
+ INSERT INTO connections (id, name, engine, connection_type, host, port, database, username, encrypted_password, ssl, default_schema, tags, read_only, server_id, project_id, group_id, created_by, created_at, updated_at)
33721
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
33722
+ `).run(id, input.name, input.engine, connectionType, input.host, input.port, input.database, input.username, encryptedPwd, input.ssl ? 1 : 0, input.defaultSchema || null, JSON.stringify(input.tags ?? []), input.readOnly ? 1 : 0, input.serverId || null, input.projectId || null, input.groupId || null, userId || null, now, now);
33639
33723
  return this.findById(id);
33640
33724
  }
33641
33725
  /**
@@ -33675,10 +33759,10 @@ var ConnectionRepository = class {
33675
33759
  return row ? this.rowToConnection(row) : null;
33676
33760
  }
33677
33761
  /**
33678
- * Get all connections
33762
+ * Get all connections (filtered by user unless admin)
33679
33763
  */
33680
- findAll() {
33681
- const rows = this.db.prepare(`
33764
+ findAll(userContext) {
33765
+ let query = `
33682
33766
  SELECT
33683
33767
  c.*,
33684
33768
  s.name as server_name,
@@ -33688,15 +33772,21 @@ var ConnectionRepository = class {
33688
33772
  LEFT JOIN servers s ON c.server_id = s.id
33689
33773
  LEFT JOIN projects p ON c.project_id = p.id
33690
33774
  LEFT JOIN database_groups dg ON c.group_id = dg.id
33691
- ORDER BY p.name, dg.name, c.name
33692
- `).all();
33775
+ `;
33776
+ const params = [];
33777
+ if (userContext && !userContext.isAdmin && userContext.userId) {
33778
+ query += ` WHERE c.created_by = ? OR c.created_by IS NULL`;
33779
+ params.push(userContext.userId);
33780
+ }
33781
+ query += ` ORDER BY p.name, dg.name, c.name`;
33782
+ const rows = this.db.prepare(query).all(...params);
33693
33783
  return rows.map((row) => this.rowToConnection(row));
33694
33784
  }
33695
33785
  /**
33696
- * Find connections by project
33786
+ * Find connections by project (filtered by user unless admin)
33697
33787
  */
33698
- findByProject(projectId) {
33699
- const rows = this.db.prepare(`
33788
+ findByProject(projectId, userContext) {
33789
+ let query = `
33700
33790
  SELECT
33701
33791
  c.*,
33702
33792
  s.name as server_name,
@@ -33707,15 +33797,21 @@ var ConnectionRepository = class {
33707
33797
  LEFT JOIN projects p ON c.project_id = p.id
33708
33798
  LEFT JOIN database_groups dg ON c.group_id = dg.id
33709
33799
  WHERE c.project_id = ?
33710
- ORDER BY dg.name, c.name
33711
- `).all(projectId);
33800
+ `;
33801
+ const params = [projectId];
33802
+ if (userContext && !userContext.isAdmin && userContext.userId) {
33803
+ query += ` AND (c.created_by = ? OR c.created_by IS NULL)`;
33804
+ params.push(userContext.userId);
33805
+ }
33806
+ query += ` ORDER BY dg.name, c.name`;
33807
+ const rows = this.db.prepare(query).all(...params);
33712
33808
  return rows.map((row) => this.rowToConnection(row));
33713
33809
  }
33714
33810
  /**
33715
- * Find connections by database group
33811
+ * Find connections by database group (filtered by user unless admin)
33716
33812
  */
33717
- findByGroup(groupId) {
33718
- const rows = this.db.prepare(`
33813
+ findByGroup(groupId, userContext) {
33814
+ let query = `
33719
33815
  SELECT
33720
33816
  c.*,
33721
33817
  s.name as server_name,
@@ -33726,15 +33822,21 @@ var ConnectionRepository = class {
33726
33822
  LEFT JOIN projects p ON c.project_id = p.id
33727
33823
  LEFT JOIN database_groups dg ON c.group_id = dg.id
33728
33824
  WHERE c.group_id = ?
33729
- ORDER BY c.name
33730
- `).all(groupId);
33825
+ `;
33826
+ const params = [groupId];
33827
+ if (userContext && !userContext.isAdmin && userContext.userId) {
33828
+ query += ` AND (c.created_by = ? OR c.created_by IS NULL)`;
33829
+ params.push(userContext.userId);
33830
+ }
33831
+ query += ` ORDER BY c.name`;
33832
+ const rows = this.db.prepare(query).all(...params);
33731
33833
  return rows.map((row) => this.rowToConnection(row));
33732
33834
  }
33733
33835
  /**
33734
- * Find ungrouped connections (no project assigned)
33836
+ * Find ungrouped connections (no project assigned, filtered by user unless admin)
33735
33837
  */
33736
- findUngrouped() {
33737
- const rows = this.db.prepare(`
33838
+ findUngrouped(userContext) {
33839
+ let query = `
33738
33840
  SELECT
33739
33841
  c.*,
33740
33842
  s.name as server_name,
@@ -33745,15 +33847,21 @@ var ConnectionRepository = class {
33745
33847
  LEFT JOIN projects p ON c.project_id = p.id
33746
33848
  LEFT JOIN database_groups dg ON c.group_id = dg.id
33747
33849
  WHERE c.project_id IS NULL
33748
- ORDER BY c.name
33749
- `).all();
33850
+ `;
33851
+ const params = [];
33852
+ if (userContext && !userContext.isAdmin && userContext.userId) {
33853
+ query += ` AND (c.created_by = ? OR c.created_by IS NULL)`;
33854
+ params.push(userContext.userId);
33855
+ }
33856
+ query += ` ORDER BY c.name`;
33857
+ const rows = this.db.prepare(query).all(...params);
33750
33858
  return rows.map((row) => this.rowToConnection(row));
33751
33859
  }
33752
33860
  /**
33753
- * Find connections by tag
33861
+ * Find connections by tag (filtered by user unless admin)
33754
33862
  */
33755
- findByTag(tag) {
33756
- const rows = this.db.prepare(`
33863
+ findByTag(tag, userContext) {
33864
+ let query = `
33757
33865
  SELECT
33758
33866
  c.*,
33759
33867
  s.name as server_name,
@@ -33763,8 +33871,14 @@ var ConnectionRepository = class {
33763
33871
  LEFT JOIN servers s ON c.server_id = s.id
33764
33872
  LEFT JOIN projects p ON c.project_id = p.id
33765
33873
  LEFT JOIN database_groups dg ON c.group_id = dg.id
33766
- ORDER BY c.name
33767
- `).all();
33874
+ `;
33875
+ const params = [];
33876
+ if (userContext && !userContext.isAdmin && userContext.userId) {
33877
+ query += ` WHERE c.created_by = ? OR c.created_by IS NULL`;
33878
+ params.push(userContext.userId);
33879
+ }
33880
+ query += ` ORDER BY c.name`;
33881
+ const rows = this.db.prepare(query).all(...params);
33768
33882
  return rows.filter((row) => {
33769
33883
  const tags = JSON.parse(row.tags);
33770
33884
  return tags.includes(tag);
@@ -33905,27 +34019,45 @@ var ConnectionRepository = class {
33905
34019
  serverId: row.server_id || void 0,
33906
34020
  projectId: row.project_id || void 0,
33907
34021
  groupId: row.group_id || void 0,
34022
+ createdBy: row.created_by || void 0,
33908
34023
  serverName: row.server_name,
33909
34024
  projectName: row.project_name,
33910
34025
  groupName: row.group_name
33911
34026
  };
33912
34027
  }
33913
34028
  /**
33914
- * Find connections by server ID
34029
+ * Check if user can access a connection
33915
34030
  */
33916
- findByServerId(serverId) {
33917
- const rows = this.db.prepare(`
33918
- SELECT c.*,
33919
- s.name as server_name,
33920
- p.name as project_name,
33921
- g.name as group_name
33922
- FROM connections c
33923
- LEFT JOIN servers s ON c.server_id = s.id
33924
- LEFT JOIN projects p ON c.project_id = p.id
33925
- LEFT JOIN database_groups g ON c.group_id = g.id
33926
- WHERE c.server_id = ?
33927
- ORDER BY c.name
33928
- `).all(serverId);
34031
+ canAccess(connectionId, userContext) {
34032
+ if (userContext.isAdmin)
34033
+ return true;
34034
+ const row = this.db.prepare("SELECT created_by FROM connections WHERE id = ?").get(connectionId);
34035
+ if (!row)
34036
+ return false;
34037
+ return row.created_by === null || row.created_by === userContext.userId;
34038
+ }
34039
+ /**
34040
+ * Find connections by server ID (filtered by user unless admin)
34041
+ */
34042
+ findByServerId(serverId, userContext) {
34043
+ let query = `
34044
+ SELECT c.*,
34045
+ s.name as server_name,
34046
+ p.name as project_name,
34047
+ g.name as group_name
34048
+ FROM connections c
34049
+ LEFT JOIN servers s ON c.server_id = s.id
34050
+ LEFT JOIN projects p ON c.project_id = p.id
34051
+ LEFT JOIN database_groups g ON c.group_id = g.id
34052
+ WHERE c.server_id = ?
34053
+ `;
34054
+ const params = [serverId];
34055
+ if (userContext && !userContext.isAdmin && userContext.userId) {
34056
+ query += ` AND (c.created_by = ? OR c.created_by IS NULL)`;
34057
+ params.push(userContext.userId);
34058
+ }
34059
+ query += ` ORDER BY c.name`;
34060
+ const rows = this.db.prepare(query).all(...params);
33929
34061
  return rows.map((row) => this.rowToConnection(row));
33930
34062
  }
33931
34063
  };
@@ -33963,21 +34095,22 @@ var ServerRepository = class {
33963
34095
  stopCommand: row.stop_command ?? void 0,
33964
34096
  createdAt: new Date(row.created_at),
33965
34097
  updatedAt: new Date(row.updated_at),
34098
+ createdBy: row.created_by ?? void 0,
33966
34099
  databaseCount: row.database_count
33967
34100
  };
33968
34101
  }
33969
34102
  /**
33970
34103
  * Create a new server
33971
34104
  */
33972
- create(input) {
34105
+ create(input, userId) {
33973
34106
  const id = MetadataDatabase.generateId();
33974
34107
  const now = (/* @__PURE__ */ new Date()).toISOString();
33975
34108
  const encryptedPwd = input.password ? encryptPassword(input.password) : null;
33976
34109
  const connectionType = input.connectionType || this.inferConnectionType(input.host);
33977
34110
  this.db.prepare(`
33978
- INSERT INTO servers (id, name, engine, connection_type, host, port, username, encrypted_password, ssl, tags, start_command, stop_command, created_at, updated_at)
33979
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
33980
- `).run(id, input.name, input.engine, connectionType, input.host, input.port, input.username, encryptedPwd, input.ssl ? 1 : 0, JSON.stringify(input.tags ?? []), input.startCommand ?? null, input.stopCommand ?? null, now, now);
34111
+ INSERT INTO servers (id, name, engine, connection_type, host, port, username, encrypted_password, ssl, tags, start_command, stop_command, created_by, created_at, updated_at)
34112
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
34113
+ `).run(id, input.name, input.engine, connectionType, input.host, input.port, input.username, encryptedPwd, input.ssl ? 1 : 0, JSON.stringify(input.tags ?? []), input.startCommand ?? null, input.stopCommand ?? null, userId || null, now, now);
33981
34114
  return this.findById(id);
33982
34115
  }
33983
34116
  /**
@@ -34005,30 +34138,53 @@ var ServerRepository = class {
34005
34138
  return row ? this.rowToServer(row) : null;
34006
34139
  }
34007
34140
  /**
34008
- * Find all servers
34141
+ * Find all servers (filtered by user unless admin)
34009
34142
  */
34010
- findAll() {
34011
- const rows = this.db.prepare(`
34012
- SELECT s.*,
34013
- (SELECT COUNT(*) FROM connections c WHERE c.server_id = s.id) as database_count
34014
- FROM servers s
34015
- ORDER BY s.name
34016
- `).all();
34143
+ findAll(userContext) {
34144
+ let query = `
34145
+ SELECT s.*,
34146
+ (SELECT COUNT(*) FROM connections c WHERE c.server_id = s.id) as database_count
34147
+ FROM servers s
34148
+ `;
34149
+ const params = [];
34150
+ if (userContext && !userContext.isAdmin && userContext.userId) {
34151
+ query += ` WHERE s.created_by = ? OR s.created_by IS NULL`;
34152
+ params.push(userContext.userId);
34153
+ }
34154
+ query += ` ORDER BY s.name`;
34155
+ const rows = this.db.prepare(query).all(...params);
34017
34156
  return rows.map((row) => this.rowToServer(row));
34018
34157
  }
34019
34158
  /**
34020
- * Find servers by engine
34159
+ * Find servers by engine (filtered by user unless admin)
34021
34160
  */
34022
- findByEngine(engine) {
34023
- const rows = this.db.prepare(`
34024
- SELECT s.*,
34025
- (SELECT COUNT(*) FROM connections c WHERE c.server_id = s.id) as database_count
34026
- FROM servers s
34027
- WHERE s.engine = ?
34028
- ORDER BY s.name
34029
- `).all(engine);
34161
+ findByEngine(engine, userContext) {
34162
+ let query = `
34163
+ SELECT s.*,
34164
+ (SELECT COUNT(*) FROM connections c WHERE c.server_id = s.id) as database_count
34165
+ FROM servers s
34166
+ WHERE s.engine = ?
34167
+ `;
34168
+ const params = [engine];
34169
+ if (userContext && !userContext.isAdmin && userContext.userId) {
34170
+ query += ` AND (s.created_by = ? OR s.created_by IS NULL)`;
34171
+ params.push(userContext.userId);
34172
+ }
34173
+ query += ` ORDER BY s.name`;
34174
+ const rows = this.db.prepare(query).all(...params);
34030
34175
  return rows.map((row) => this.rowToServer(row));
34031
34176
  }
34177
+ /**
34178
+ * Check if user can access a server
34179
+ */
34180
+ canAccess(serverId, userContext) {
34181
+ if (userContext.isAdmin)
34182
+ return true;
34183
+ const row = this.db.prepare("SELECT created_by FROM servers WHERE id = ?").get(serverId);
34184
+ if (!row)
34185
+ return false;
34186
+ return row.created_by === null || row.created_by === userContext.userId;
34187
+ }
34032
34188
  /**
34033
34189
  * Update a server
34034
34190
  */
@@ -34123,7 +34279,7 @@ var ServerRepository = class {
34123
34279
  };
34124
34280
 
34125
34281
  // apps/cli/dist/version.js
34126
- var VERSION = true ? "0.3.1" : "0.1.10";
34282
+ var VERSION = true ? "0.4.0" : "0.1.10";
34127
34283
 
34128
34284
  // apps/cli/dist/commands/init.js
34129
34285
  function loadEnvFile(cwd) {