use async_trait::async_trait;
use crate::{Error as PostgresError, Privilege};
use lmrc_ports::{
CreatedDatabase, DatabaseCreateRequest, DatabaseProvider, DatabaseUser, DatabaseUserRequest,
PortError, PortResult,
};
use lmrc_ssh::{AuthMethod, SshClient};
pub struct PostgresAdapter;
impl PostgresAdapter {
pub fn new() -> Self {
Self
}
async fn create_ssh_client(&self, host: &str, ssh_key_path: &str) -> PortResult<SshClient> {
let ssh_auth = AuthMethod::PublicKey {
username: "root".to_string(),
private_key_path: ssh_key_path.to_string(),
passphrase: None,
};
let client = SshClient::new(host, 22)
.map_err(|e| PortError::NetworkError(format!("Failed to create SSH client: {}", e)))?
.with_auth(ssh_auth)
.connect()
.map_err(|e| {
PortError::NetworkError(format!("Failed to connect via SSH: {}", e))
})?;
Ok(client)
}
}
fn convert_error(err: PostgresError) -> PortError {
match err {
PostgresError::Ssh(e) => PortError::NetworkError(format!("SSH error: {}", e)),
PostgresError::SshExecution { message, command } => PortError::OperationFailed(format!(
"SSH command '{}' failed: {}",
command, message
)),
PostgresError::Installation(msg) => PortError::OperationFailed(format!("Installation failed: {}", msg)),
PostgresError::Configuration(msg) => PortError::InvalidConfiguration(msg),
PostgresError::NotInstalled => PortError::OperationFailed("PostgreSQL is not installed".to_string()),
PostgresError::AlreadyInstalled(version) => PortError::AlreadyExists {
resource_type: "PostgreSQL installation".to_string(),
resource_id: version,
},
PostgresError::InvalidVersion(version) => {
PortError::InvalidConfiguration(format!("Invalid PostgreSQL version: {}", version))
}
PostgresError::InvalidConfig { parameter, value } => {
PortError::InvalidConfiguration(format!("Invalid config {} = {}", parameter, value))
}
PostgresError::MissingConfig(msg) => PortError::InvalidConfiguration(format!("Missing config: {}", msg)),
PostgresError::ServiceError(msg) => PortError::OperationFailed(format!("Service error: {}", msg)),
PostgresError::ConnectionTest(msg) => {
PortError::OperationFailed(format!("Connection test failed: {}", msg))
}
PostgresError::Uninstallation(msg) => {
PortError::OperationFailed(format!("Uninstallation failed: {}", msg))
}
PostgresError::Io(e) => PortError::OperationFailed(format!("IO error: {}", e)),
PostgresError::Serialization(e) => PortError::OperationFailed(format!("Serialization error: {}", e)),
PostgresError::Other(msg) => PortError::OperationFailed(msg),
}
}
#[async_trait]
impl DatabaseProvider for PostgresAdapter {
async fn create_database(&self, request: DatabaseCreateRequest) -> PortResult<CreatedDatabase> {
let mut ssh = self.create_ssh_client(&request.host, &request.ssh_key_path).await?;
crate::create_database_with_options(
&mut ssh,
&request.name,
Some(&request.owner),
request.encoding.as_deref(),
None, )
.await
.map_err(convert_error)?;
let connection_string = format!(
"postgresql://{}@{}:{}/{}",
request.owner, request.host, request.port, request.name
);
Ok(CreatedDatabase {
name: request.name.clone(),
owner: request.owner.clone(),
connection_string,
host: request.host,
port: request.port,
})
}
async fn drop_database(&self, name: &str, host: &str, port: u16, ssh_key_path: &str) -> PortResult<()> {
let mut ssh = self.create_ssh_client(host, ssh_key_path).await?;
crate::drop_database(&mut ssh, name)
.await
.map_err(convert_error)?;
Ok(())
}
async fn list_databases(&self, host: &str, port: u16, ssh_key_path: &str) -> PortResult<Vec<String>> {
let mut ssh = self.create_ssh_client(host, ssh_key_path).await?;
let databases = crate::list_databases(&mut ssh)
.await
.map_err(convert_error)?;
Ok(databases.into_iter().map(|db| db.name).collect())
}
async fn database_exists(&self, name: &str, host: &str, port: u16, ssh_key_path: &str) -> PortResult<bool> {
let mut ssh = self.create_ssh_client(host, ssh_key_path).await?;
crate::database_exists(&mut ssh, name)
.await
.map_err(convert_error)
}
async fn create_user(&self, request: DatabaseUserRequest) -> PortResult<DatabaseUser> {
let mut ssh = self.create_ssh_client(&request.host, &request.ssh_key_path).await?;
crate::create_user_with_options(
&mut ssh,
&request.username,
&request.password,
request.superuser,
false, false, Some(-1), )
.await
.map_err(convert_error)?;
Ok(DatabaseUser {
username: request.username,
superuser: request.superuser,
})
}
async fn drop_user(&self, username: &str, host: &str, port: u16, ssh_key_path: &str) -> PortResult<()> {
let mut ssh = self.create_ssh_client(host, ssh_key_path).await?;
crate::drop_user(&mut ssh, username)
.await
.map_err(convert_error)?;
Ok(())
}
async fn grant_privileges(&self, database: &str, username: &str, host: &str, port: u16, ssh_key_path: &str) -> PortResult<()> {
let mut ssh = self.create_ssh_client(host, ssh_key_path).await?;
crate::grant_privileges(
&mut ssh,
database,
username,
&[Privilege::All],
)
.await
.map_err(convert_error)?;
Ok(())
}
}