Skip to content

Commit fc1e090

Browse files
eddietejedaclaude
andcommitted
Scope current database per workspace; clear on delete
- `current_database: Option<String>` → `current_databases: HashMap<String, String>` keyed by workspace_id, so switching workspaces no longer inherits a database set in a different workspace. - `ApiClient::new()` now passes the resolved workspace_id into `load_current_database`, so `X-Database-Id` is only sent when it belongs to the active workspace. - `print_database_footer()` reads `ACTIVE_WORKSPACE_ID` (set by `resolve_workspace()`) so the footer shows the correct database for the workspace actually used. - `databases::delete()` clears `current_databases[workspace_id]` when the deleted database was the current one, preventing subsequent commands from silently sending a stale `X-Database-Id`. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent df44c0a commit fc1e090

4 files changed

Lines changed: 56 additions & 18 deletions

File tree

src/api.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ impl ApiClient {
118118
}
119119
profile_config.sandbox
120120
}),
121-
database_id: crate::config::load_current_database("default"),
121+
database_id: workspace_id.and_then(|ws| crate::config::load_current_database("default", ws)),
122122
}
123123
}
124124

src/config.rs

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,8 @@ pub struct ProfileConfig {
101101
pub workspaces: Vec<WorkspaceEntry>,
102102
#[serde(default, skip_serializing_if = "Option::is_none", alias = "session")]
103103
pub sandbox: Option<String>,
104-
#[serde(default, skip_serializing_if = "Option::is_none")]
105-
pub current_database: Option<String>,
104+
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
105+
pub current_databases: HashMap<String, String>,
106106
}
107107

108108
#[derive(Debug, Deserialize, Serialize)]
@@ -229,7 +229,7 @@ pub fn clear_sandbox(profile: &str) -> Result<(), String> {
229229
write_config(&config_path, &content)
230230
}
231231

232-
pub fn save_current_database(profile: &str, database_id: &str) -> Result<(), String> {
232+
pub fn save_current_database(profile: &str, workspace_id: &str, database_id: &str) -> Result<(), String> {
233233
let config_path = config_path()?;
234234

235235
let mut config_file: ConfigFile = if config_path.exists() {
@@ -244,21 +244,43 @@ pub fn save_current_database(profile: &str, database_id: &str) -> Result<(), Str
244244
.profiles
245245
.entry(profile.to_string())
246246
.or_default()
247-
.current_database = Some(database_id.to_string());
247+
.current_databases
248+
.insert(workspace_id.to_string(), database_id.to_string());
248249

249250
let content = serde_yaml::to_string(&config_file)
250251
.map_err(|e| format!("error serializing config: {e}"))?;
251252
write_config(&config_path, &content)
252253
}
253254

254-
pub fn load_current_database(profile: &str) -> Option<String> {
255+
pub fn load_current_database(profile: &str, workspace_id: &str) -> Option<String> {
255256
let config_path = config_path().ok()?;
256257
if !config_path.exists() {
257258
return None;
258259
}
259260
let content = fs::read_to_string(&config_path).ok()?;
260261
let config_file: ConfigFile = serde_yaml::from_str(&content).ok()?;
261-
config_file.profiles.get(profile)?.current_database.clone()
262+
config_file.profiles.get(profile)?.current_databases.get(workspace_id).cloned()
263+
}
264+
265+
pub fn clear_current_database(profile: &str, workspace_id: &str) -> Result<(), String> {
266+
let config_path = config_path()?;
267+
268+
if !config_path.exists() {
269+
return Ok(());
270+
}
271+
272+
let content = fs::read_to_string(&config_path)
273+
.map_err(|e| format!("error reading config file: {e}"))?;
274+
let mut config_file: ConfigFile =
275+
serde_yaml::from_str(&content).map_err(|e| format!("error parsing config file: {e}"))?;
276+
277+
if let Some(entry) = config_file.profiles.get_mut(profile) {
278+
entry.current_databases.remove(workspace_id);
279+
}
280+
281+
let content = serde_yaml::to_string(&config_file)
282+
.map_err(|e| format!("error serializing config: {e}"))?;
283+
write_config(&config_path, &content)
262284
}
263285

264286
pub fn resolve_workspace_id(provided: Option<String>, profile_config: &ProfileConfig) -> Result<String, String> {

src/databases.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ pub fn create(
436436
}
437437
};
438438

439-
let _ = crate::config::save_current_database("default", &result.id);
439+
let _ = crate::config::save_current_database("default", workspace_id, &result.id);
440440

441441
match format {
442442
"json" => println!("{}", serde_json::to_string_pretty(&result).unwrap()),
@@ -456,18 +456,18 @@ pub fn set(id_or_description: &str, workspace_id: &str) {
456456
use crossterm::style::Stylize;
457457
let api = ApiClient::new(Some(workspace_id));
458458
let db = resolve_database(&api, id_or_description);
459-
if let Err(e) = crate::config::save_current_database("default", &db.id) {
459+
if let Err(e) = crate::config::save_current_database("default", workspace_id, &db.id) {
460460
eprintln!("{}", format!("error saving current database: {e}").red());
461461
std::process::exit(1);
462462
}
463463
println!("{}", format!("Current database set to {}", db.id).green());
464464
}
465465

466-
fn resolve_current_database(provided: Option<&str>, _workspace_id: &str) -> String {
466+
fn resolve_current_database(provided: Option<&str>, workspace_id: &str) -> String {
467467
if let Some(id) = provided {
468468
return id.to_string();
469469
}
470-
match crate::config::load_current_database("default") {
470+
match crate::config::load_current_database("default", workspace_id) {
471471
Some(id) => id,
472472
None => {
473473
use crossterm::style::Stylize;
@@ -492,6 +492,12 @@ pub fn delete(workspace_id: &str, id_or_description: &str) {
492492
std::process::exit(1);
493493
}
494494

495+
// If the deleted database was the current one, clear it so subsequent
496+
// commands don't silently send a stale X-Database-Id header.
497+
if crate::config::load_current_database("default", workspace_id).as_deref() == Some(&db.id) {
498+
let _ = crate::config::clear_current_database("default", workspace_id);
499+
}
500+
495501
println!("{}", "Database deleted.".green());
496502
}
497503

src/main.rs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ struct Cli {
5656
command: Option<Commands>,
5757
}
5858

59+
/// Set once after workspace resolution so the database footer can reference it
60+
/// without re-doing config I/O.
61+
static ACTIVE_WORKSPACE_ID: std::sync::OnceLock<String> = std::sync::OnceLock::new();
62+
5963
fn resolve_workspace(provided: Option<String>) -> String {
6064
// HOTDATA_WORKSPACE env var takes priority and blocks --workspace-id flag
6165
if let Ok(ws) = std::env::var("HOTDATA_WORKSPACE") {
@@ -67,6 +71,7 @@ fn resolve_workspace(provided: Option<String>) -> String {
6771
);
6872
std::process::exit(1);
6973
}
74+
let _ = ACTIVE_WORKSPACE_ID.set(ws.clone());
7075
return ws;
7176
}
7277
if sandbox::find_sandbox_run_ancestor().is_some() {
@@ -75,7 +80,10 @@ fn resolve_workspace(provided: Option<String>) -> String {
7580
}
7681
match config::load("default") {
7782
Ok(profile) => match config::resolve_workspace_id(provided, &profile) {
78-
Ok(id) => id,
83+
Ok(id) => {
84+
let _ = ACTIVE_WORKSPACE_ID.set(id.clone());
85+
id
86+
}
7987
Err(e) => {
8088
eprintln!("error: {e}");
8189
std::process::exit(1);
@@ -127,12 +135,14 @@ extern "C" fn print_sandbox_footer() {
127135

128136
extern "C" fn print_database_footer() {
129137
use crossterm::style::Stylize;
130-
if let Some(id) = config::load_current_database("default") {
131-
eprintln!(
132-
"{}",
133-
format!("current database: {id} use 'hotdata databases set' to change")
134-
.dark_grey(),
135-
);
138+
if let Some(ws_id) = ACTIVE_WORKSPACE_ID.get() {
139+
if let Some(id) = config::load_current_database("default", ws_id) {
140+
eprintln!(
141+
"{}",
142+
format!("current database: {id} use 'hotdata databases set' to change")
143+
.dark_grey(),
144+
);
145+
}
136146
}
137147
}
138148

0 commit comments

Comments
 (0)