Skip to content

Commit 07253d0

Browse files
feat(database-ui): standardize json api paths
1 parent 0f99484 commit 07253d0

File tree

2 files changed

+45
-80
lines changed

2 files changed

+45
-80
lines changed

packages/cli/src/main/kotlin/elide/tool/cli/cmd/db/DbStudioCommand.kt

Lines changed: 36 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -77,83 +77,48 @@ internal class DbStudioCommand : AbstractSubcommand<ToolState, CommandContext>()
7777
private val SQLITE_EXTENSIONS = setOf(".db", ".sqlite", ".sqlite3", ".db3")
7878
}
7979

80-
private fun discoverDatabases(): List<DiscoveredDatabase> {
81-
val databases = mutableListOf<DiscoveredDatabase>()
82-
83-
val cwd = Path.of(System.getProperty("user.dir"))
84-
85-
searchDirectory(cwd, databases, depth = 0, maxDepth = 0, isLocal = true)
86-
87-
try {
88-
cwd.listDirectoryEntries()
89-
.filter { it.isDirectory() }
90-
.forEach { subDir ->
91-
searchDirectory(subDir, databases, depth = 1, maxDepth = 1, isLocal = true)
92-
}
93-
} catch (e: Exception) {
94-
95-
}
96-
97-
val userHome = Path.of(System.getProperty("user.home"))
98-
val osName = System.getProperty("os.name").lowercase()
99-
100-
val userDataDirs = when {
101-
osName.contains("mac") -> listOf(
102-
userHome.resolve("Library/Application Support")
103-
)
104-
osName.contains("win") -> listOf(
105-
Path.of(System.getenv("APPDATA") ?: userHome.resolve("AppData/Roaming").toString())
106-
)
107-
else -> listOf( // Linux/Unix
108-
userHome.resolve(".local/share")
109-
)
110-
}
111-
112-
userDataDirs.forEach { dir ->
113-
if (dir.exists() && dir.isDirectory()) {
114-
searchDirectory(dir, databases, depth = 0, maxDepth = 1, isLocal = false)
115-
}
116-
}
117-
118-
return databases.sortedWith(
119-
compareByDescending<DiscoveredDatabase> { it.isLocal }
120-
.thenByDescending { it.lastModified }
80+
// Extension function for SQLite detection
81+
private fun Path.isSqliteDatabase(): Boolean =
82+
isRegularFile() && SQLITE_EXTENSIONS.any { name.endsWith(it, ignoreCase = true) }
83+
84+
// Extension function to safely convert Path to DiscoveredDatabase
85+
private fun Path.toDiscoveredDatabase(): DiscoveredDatabase? = runCatching {
86+
DiscoveredDatabase(
87+
path = toAbsolutePath().toString(),
88+
name = name,
89+
size = fileSize(),
90+
lastModified = getLastModifiedTime().toMillis(),
91+
isLocal = true,
12192
)
122-
}
93+
}.getOrNull()
12394

95+
// Functional version that returns a list
12496
private fun searchDirectory(
12597
dir: Path,
126-
databases: MutableList<DiscoveredDatabase>,
12798
depth: Int,
128-
maxDepth: Int,
129-
isLocal: Boolean
130-
) {
131-
try {
132-
dir.listDirectoryEntries().forEach { file ->
133-
when {
134-
file.isRegularFile() && SQLITE_EXTENSIONS.any { file.name.endsWith(it, ignoreCase = true) } -> {
135-
try {
136-
databases.add(
137-
DiscoveredDatabase(
138-
path = file.toAbsolutePath().toString(),
139-
name = file.name,
140-
size = file.fileSize(),
141-
lastModified = file.getLastModifiedTime().toMillis(),
142-
isLocal = isLocal,
143-
)
144-
)
145-
} catch (e: Exception) {
146-
// Silently ignore files we can't read
147-
}
148-
}
149-
file.isDirectory() && depth < maxDepth -> {
150-
searchDirectory(file, databases, depth + 1, maxDepth, isLocal)
151-
}
152-
}
99+
maxDepth: Int
100+
): List<DiscoveredDatabase> = runCatching {
101+
dir.listDirectoryEntries().flatMap { file ->
102+
when {
103+
file.isSqliteDatabase() -> listOfNotNull(file.toDiscoveredDatabase())
104+
file.isDirectory() && depth < maxDepth -> searchDirectory(file, depth + 1, maxDepth)
105+
else -> emptyList()
153106
}
154-
} catch (e: Exception) {
155-
// Silently ignore permission errors
156107
}
108+
}.getOrElse { emptyList() }
109+
110+
private fun discoverDatabases(): List<DiscoveredDatabase> {
111+
val cwd = Path.of(System.getProperty("user.dir"))
112+
113+
// Search current directory and immediate subdirectories
114+
val currentDir = searchDirectory(cwd, depth = 0, maxDepth = 0)
115+
val subDirs = runCatching {
116+
cwd.listDirectoryEntries()
117+
.filter { it.isDirectory() }
118+
.flatMap { searchDirectory(it, depth = 1, maxDepth = 1) }
119+
}.getOrElse { emptyList() }
120+
121+
return (currentDir + subDirs).sortedByDescending { it.lastModified }
157122
}
158123

159124
private fun copyDirectory(sourcePath: Path, targetDir: Path) {
@@ -233,7 +198,7 @@ internal class DbStudioCommand : AbstractSubcommand<ToolState, CommandContext>()
233198
val discovered = discoverDatabases()
234199

235200
if (discovered.isEmpty()) {
236-
return CommandResult.err(message = "No SQLite databases found in current directory or user data directories")
201+
return CommandResult.err(message = "No SQLite databases found in current directory or user data directories. If you're targeting a database file outside of this directory, provide the path as an argument (e.g. \"elide db studio /my/database/path.db\")")
237202
}
238203

239204
discovered

packages/cli/src/projects/db-studio/api/server.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,10 @@ export function startServer({ port, databases, Database }: ServerConfig): void {
6767
});
6868

6969
/**
70-
* GET /api/database/:dbIndex
70+
* GET /api/databases/:dbIndex
7171
* Get metadata for a specific database
7272
*/
73-
Elide.http.router.handle("GET", "/api/database/:dbIndex", async (request, response: ElideHttpResponseExtended, context: RouteContext) => {
73+
Elide.http.router.handle("GET", "/api/databases/:dbIndex", async (request, response: ElideHttpResponseExtended, context: RouteContext) => {
7474
try {
7575
const dbIndexStr = context?.params?.dbIndex || "";
7676
const dbIndex = parseInt(dbIndexStr, 10);
@@ -110,10 +110,10 @@ export function startServer({ port, databases, Database }: ServerConfig): void {
110110
});
111111

112112
/**
113-
* GET /api/database/:dbIndex/tables
113+
* GET /api/databases/:dbIndex/tables
114114
* List all tables in a database
115115
*/
116-
Elide.http.router.handle("GET", "/api/database/:dbIndex/tables", async (request, response: ElideHttpResponseExtended, context: RouteContext) => {
116+
Elide.http.router.handle("GET", "/api/databases/:dbIndex/tables", async (request, response: ElideHttpResponseExtended, context: RouteContext) => {
117117
try {
118118
const dbIndexStr = context?.params?.dbIndex || "";
119119
const dbIndex = parseInt(dbIndexStr, 10);
@@ -144,10 +144,10 @@ export function startServer({ port, databases, Database }: ServerConfig): void {
144144
});
145145

146146
/**
147-
* GET /api/database/:dbIndex/table/:tableName
147+
* GET /api/databases/:dbIndex/tables/:tableName
148148
* Get data from a specific table
149149
*/
150-
Elide.http.router.handle("GET", "/api/database/:dbIndex/table/:tableName", async (request, response: ElideHttpResponseExtended, context: RouteContext) => {
150+
Elide.http.router.handle("GET", "/api/databases/:dbIndex/tables/:tableName", async (request, response: ElideHttpResponseExtended, context: RouteContext) => {
151151
try {
152152
const dbIndexStr = context?.params?.dbIndex || "";
153153
const dbIndex = parseInt(dbIndexStr, 10);
@@ -206,9 +206,9 @@ export function startServer({ port, databases, Database }: ServerConfig): void {
206206
console.log();
207207
console.log("API Endpoints:");
208208
console.log(` GET /api/databases`);
209-
console.log(` GET /api/database/:dbIndex`);
210-
console.log(` GET /api/database/:dbIndex/tables`);
211-
console.log(` GET /api/database/:dbIndex/table/:tableName`);
209+
console.log(` GET /api/databases/:dbIndex`);
210+
console.log(` GET /api/databases/:dbIndex/tables`);
211+
console.log(` GET /api/databases/:dbIndex/tables/:tableName`);
212212
console.log(` GET /health`);
213213
});
214214

0 commit comments

Comments
 (0)