use actix_web::{HttpResponse, post, web};
use reqwest::{Error, Response, StatusCode};
use serde::{Deserialize, Serialize};
use std::env;
use tracing::error;
use web::{Data, Json};
use crate::AppState;
use crate::api::response::{api_success, bad_request, internal_error};
const SUPABASE_API_BASE: &str = "https://api.supabase.com/v1";
#[derive(Debug, Deserialize)]
pub struct SslEnforcementRequest {
pub enabled: bool,
pub access_token: Option<String>,
pub project_ref: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SslEnforcementResponse {
pub current_config: SslConfig,
pub applied_successfully: bool,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SslConfig {
pub database: bool,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct SslEnforcementPayload {
requested_config: SslConfig,
}
fn resolve_credentials(req: &SslEnforcementRequest) -> Result<(String, String), &'static str> {
let access_token: String = req
.access_token
.clone()
.or_else(|| env::var("SUPABASE_ACCESS_TOKEN").ok())
.filter(|s| !s.is_empty())
.ok_or("Missing Supabase access token. Provide via request body or SUPABASE_ACCESS_TOKEN env var.")?;
let project_ref: String = req
.project_ref
.clone()
.or_else(|| env::var("PROJECT_REF").ok())
.filter(|s| !s.is_empty())
.ok_or("Missing project reference. Provide via request body or PROJECT_REF env var.")?;
Ok((access_token, project_ref))
}
#[post("/api/v2/supabase/ssl_enforcement")]
pub async fn ssl_enforcement(
body: Json<SslEnforcementRequest>,
app_state: Data<AppState>,
) -> HttpResponse {
let (access_token, project_ref) = match resolve_credentials(&body) {
Ok(creds) => creds,
Err(err) => return bad_request("Missing credentials", err),
};
let url: String = format!(
"{}/projects/{}/ssl-enforcement",
SUPABASE_API_BASE, project_ref
);
let payload: SslEnforcementPayload = SslEnforcementPayload {
requested_config: SslConfig {
database: body.enabled,
},
};
let response: Result<Response, Error> = app_state
.client
.put(&url)
.bearer_auth(&access_token)
.json(&payload)
.send()
.await;
match response {
Ok(resp) => {
let status: StatusCode = resp.status();
if status.is_success() {
match resp.json::<SslEnforcementResponse>().await {
Ok(data) => {
let message: &str = if body.enabled {
"SSL enforcement enabled"
} else {
"SSL enforcement disabled"
};
api_success(message, data)
}
Err(e) => {
error!(error = %e, "Failed to parse Supabase API response");
internal_error(
"Failed to parse API response",
format!("Invalid response format: {}", e),
)
}
}
} else {
let error_text: String = resp
.text()
.await
.unwrap_or_else(|_| "Unknown error".to_string());
error!(
status = %status,
error = %error_text,
"Supabase API returned error"
);
internal_error(
"Failed to update SSL enforcement",
format!("HTTP {}: {}", status, error_text),
)
}
}
Err(e) => {
error!(error = %e, "Failed to connect to Supabase API");
internal_error(
"Failed to connect to Supabase API",
format!("Connection error: {}", e),
)
}
}
}