diff --git a/dbhub.toml.example b/dbhub.toml.example index 467a4cc..bd4e73b 100644 --- a/dbhub.toml.example +++ b/dbhub.toml.example @@ -18,6 +18,7 @@ # Local PostgreSQL - DSN format (typical Docker/local dev setup) [[sources]] id = "local_pg" +description = "Local development database" dsn = "postgres://postgres:postgres@localhost:5432/myapp" # Local PostgreSQL - Individual parameters (use when password contains special characters like @, :, /) @@ -249,6 +250,11 @@ dsn = "postgres://postgres:postgres@localhost:5432/myapp" # QUICK REFERENCE # ============================================================================ # +# Source Fields: +# id = "unique_id" # Required: unique identifier +# description = "..." # Optional: human-readable description +# dsn = "..." # Connection string (or use individual params below) +# # DSN Formats: # PostgreSQL: postgres://user:pass@host:5432/database?sslmode=require # MySQL: mysql://user:pass@host:3306/database diff --git a/src/__fixtures__/toml/readonly-maxrows.toml b/src/__fixtures__/toml/readonly-maxrows.toml index 3f6b693..fc20c0c 100644 --- a/src/__fixtures__/toml/readonly-maxrows.toml +++ b/src/__fixtures__/toml/readonly-maxrows.toml @@ -3,6 +3,7 @@ [[sources]] id = "readonly_limited" +description = "Read-only database for safe queries" type = "sqlite" database = ":memory:" diff --git a/src/api/__tests__/sources.integration.test.ts b/src/api/__tests__/sources.integration.test.ts index 19de03e..e6487d2 100644 --- a/src/api/__tests__/sources.integration.test.ts +++ b/src/api/__tests__/sources.integration.test.ts @@ -211,6 +211,19 @@ describe('Data Sources API Integration Tests', () => { expect(sqlParam!.description).toContain('SQL'); }); }); + + it('should include description when present', async () => { + const response = await fetch(`${BASE_URL}/api/sources`); + const sources = (await response.json()) as DataSource[]; + + // First source has a description + const readonlySource = sources.find(s => s.id === 'readonly_limited'); + expect(readonlySource?.description).toBe('Read-only database for safe queries'); + + // Other sources don't have descriptions + const writableSource = sources.find(s => s.id === 'writable_limited'); + expect(writableSource?.description).toBeUndefined(); + }); }); describe('GET /api/sources/{source-id}', () => { diff --git a/src/api/openapi.d.ts b/src/api/openapi.d.ts index 4c430e9..e58a45f 100644 --- a/src/api/openapi.d.ts +++ b/src/api/openapi.d.ts @@ -54,6 +54,11 @@ export interface components { * @example prod_pg */ id: string; + /** + * @description Human-readable description of the data source + * @example Production read replica for analytics queries + */ + description?: string; /** * @description Database type * @example postgres diff --git a/src/api/openapi.yaml b/src/api/openapi.yaml index 1cdc1f0..0a03eb8 100644 --- a/src/api/openapi.yaml +++ b/src/api/openapi.yaml @@ -96,6 +96,10 @@ components: type: string description: Unique identifier for the data source example: prod_pg + description: + type: string + description: Human-readable description of the data source + example: Production read replica for analytics queries type: type: string enum: [postgres, mysql, mariadb, sqlserver, sqlite] diff --git a/src/api/sources.ts b/src/api/sources.ts index c932694..f3a9db1 100644 --- a/src/api/sources.ts +++ b/src/api/sources.ts @@ -31,6 +31,11 @@ function transformSourceConfig(source: SourceConfig): DataSource { type: source.type, }; + // Add description if present + if (source.description) { + dataSource.description = source.description; + } + // Add connection details (excluding password) if (source.host) { dataSource.host = source.host; diff --git a/src/config/__tests__/toml-loader.test.ts b/src/config/__tests__/toml-loader.test.ts index 807f8fa..5580242 100644 --- a/src/config/__tests__/toml-loader.test.ts +++ b/src/config/__tests__/toml-loader.test.ts @@ -397,6 +397,37 @@ dsn = "postgres://user:pass@localhost:5432/testdb" }); }); + describe('description field', () => { + it('should parse description field', () => { + const tomlContent = ` +[[sources]] +id = "test_db" +description = "Production read replica for analytics" +dsn = "postgres://user:pass@localhost:5432/testdb" +`; + fs.writeFileSync(path.join(tempDir, 'dbhub.toml'), tomlContent); + + const result = loadTomlConfig(); + + expect(result).toBeTruthy(); + expect(result?.sources[0].description).toBe('Production read replica for analytics'); + }); + + it('should work without description (optional field)', () => { + const tomlContent = ` +[[sources]] +id = "test_db" +dsn = "postgres://user:pass@localhost:5432/testdb" +`; + fs.writeFileSync(path.join(tempDir, 'dbhub.toml'), tomlContent); + + const result = loadTomlConfig(); + + expect(result).toBeTruthy(); + expect(result?.sources[0].description).toBeUndefined(); + }); + }); + describe('sslmode validation', () => { it('should accept sslmode = "disable"', () => { const tomlContent = ` diff --git a/src/types/config.ts b/src/types/config.ts index 2ca1906..59ce98c 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -41,6 +41,7 @@ export interface ConnectionParams { */ export interface SourceConfig extends ConnectionParams, SSHConfig { id: string; + description?: string; // Human-readable description of this data source dsn?: string; connection_timeout?: number; // Connection timeout in seconds query_timeout?: number; // Query timeout in seconds (PostgreSQL, MySQL, MariaDB, SQL Server)