use serde_json::{Map, Value as JsonValue};
use crate::control::security::identity::AuthenticatedIdentity;
use crate::control::server::response_shape::types::ShapedRows;
use crate::control::state::SharedState;
use super::super::result::{DdlError, DdlResult};
fn err(sqlstate: &str, message: impl Into<String>) -> DdlError {
DdlError {
sqlstate: sqlstate.to_string(),
message: message.into(),
}
}
fn status(command: impl Into<String>) -> Vec<DdlResult> {
vec![DdlResult::Status {
command: command.into(),
rows_affected: None,
}]
}
pub fn handle_org(
state: &SharedState,
identity: &AuthenticatedIdentity,
parts: &[&str],
) -> Result<Vec<DdlResult>, DdlError> {
if parts.is_empty() {
return Err(err("42601", "empty org command"));
}
let cmd = parts[0].to_uppercase();
match cmd.as_str() {
"CREATE" => create_org(state, identity, parts),
"ALTER" => alter_org(state, identity, parts),
"DROP" => drop_org(state, identity, parts),
_ => Err(err("42601", "expected CREATE ORG, ALTER ORG, or DROP ORG")),
}
}
fn create_org(
state: &SharedState,
identity: &AuthenticatedIdentity,
parts: &[&str],
) -> Result<Vec<DdlResult>, DdlError> {
if !identity.is_superuser {
return Err(err("42501", "permission denied: requires superuser"));
}
if parts.len() < 3 {
return Err(err("42601", "syntax: CREATE ORG '<name>' [IN TENANT <id>]"));
}
let org_id = parts[2].trim_matches('\'');
let tenant_id = parts
.iter()
.position(|p| p.to_uppercase() == "TENANT")
.and_then(|i| parts.get(i + 1))
.and_then(|s| s.parse::<u64>().ok())
.unwrap_or_else(|| identity.tenant_id.as_u64());
state
.orgs
.create_org(org_id, org_id, tenant_id)
.map_err(|e| err("23505", e.to_string()))?;
state.audit_record(
crate::control::security::audit::AuditEvent::AdminAction,
Some(identity.tenant_id),
&identity.username,
&format!("created org '{org_id}' in tenant {tenant_id}"),
);
Ok(status("CREATE ORG"))
}
fn alter_org(
state: &SharedState,
identity: &AuthenticatedIdentity,
parts: &[&str],
) -> Result<Vec<DdlResult>, DdlError> {
if !identity.is_superuser {
return Err(err("42501", "permission denied: requires superuser"));
}
if parts.len() < 6 {
return Err(err("42601", "syntax: ALTER ORG '<id>' SET STATUS <status>"));
}
let org_id = parts[2].trim_matches('\'');
let status_val = parts[5].to_lowercase();
let found = state
.orgs
.set_status(org_id, &status_val)
.map_err(|e| err("XX000", e.to_string()))?;
if !found {
return Err(err("42704", format!("org '{org_id}' not found")));
}
state.audit_record(
crate::control::security::audit::AuditEvent::AdminAction,
Some(identity.tenant_id),
&identity.username,
&format!("org '{org_id}' status set to {status_val}"),
);
Ok(status("ALTER ORG"))
}
fn drop_org(
state: &SharedState,
identity: &AuthenticatedIdentity,
parts: &[&str],
) -> Result<Vec<DdlResult>, DdlError> {
if !identity.is_superuser {
return Err(err("42501", "permission denied: requires superuser"));
}
if parts.len() < 3 {
return Err(err("42601", "syntax: DROP ORG '<org_id>'"));
}
let org_id = parts[2].trim_matches('\'');
let found = state
.orgs
.drop_org(org_id)
.map_err(|e| err("XX000", e.to_string()))?;
if !found {
return Err(err("42704", format!("org '{org_id}' not found")));
}
state.audit_record(
crate::control::security::audit::AuditEvent::AdminAction,
Some(identity.tenant_id),
&identity.username,
&format!("dropped org '{org_id}'"),
);
Ok(status("DROP ORG"))
}
pub fn show_orgs(
state: &SharedState,
_identity: &AuthenticatedIdentity,
parts: &[&str],
) -> Result<Vec<DdlResult>, DdlError> {
let tenant_filter = parts
.iter()
.position(|p| p.to_uppercase() == "TENANT")
.and_then(|i| parts.get(i + 1))
.and_then(|s| s.parse::<u64>().ok());
let orgs = state.orgs.list(tenant_filter);
let columns = vec![
"org_id".to_string(),
"name".to_string(),
"tenant_id".to_string(),
"status".to_string(),
];
let column_types = ShapedRows::text_types(columns.len());
let rows: Vec<_> = orgs
.iter()
.map(|o| {
let mut row = Map::new();
row.insert("org_id".to_string(), JsonValue::String(o.org_id.clone()));
row.insert("name".to_string(), JsonValue::String(o.name.clone()));
row.insert(
"tenant_id".to_string(),
JsonValue::String(o.tenant_id.to_string()),
);
row.insert("status".to_string(), JsonValue::String(o.status.clone()));
row
})
.collect();
Ok(vec![DdlResult::Rows(ShapedRows {
columns,
column_types,
rows,
notice: None,
})])
}
pub fn show_members(
state: &SharedState,
_identity: &AuthenticatedIdentity,
parts: &[&str],
) -> Result<Vec<DdlResult>, DdlError> {
let org_id = parts
.iter()
.position(|p| p.to_uppercase() == "ORG")
.and_then(|i| parts.get(i + 1))
.map(|s| s.trim_matches('\''))
.ok_or_else(|| err("42601", "syntax: SHOW MEMBERS OF ORG '<org_id>'"))?;
let members = state.orgs.members_of(org_id);
let columns = vec![
"user_id".to_string(),
"org_id".to_string(),
"role".to_string(),
"joined_at".to_string(),
];
let column_types = ShapedRows::text_types(columns.len());
let rows: Vec<_> = members
.iter()
.map(|m| {
let mut row = Map::new();
row.insert(
"user_id".to_string(),
JsonValue::String(m.auth_user_id.clone()),
);
row.insert("org_id".to_string(), JsonValue::String(m.org_id.clone()));
row.insert("role".to_string(), JsonValue::String(m.role.clone()));
row.insert(
"joined_at".to_string(),
JsonValue::String(m.joined_at.to_string()),
);
row
})
.collect();
Ok(vec![DdlResult::Rows(ShapedRows {
columns,
column_types,
rows,
notice: None,
})])
}