Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions src/http/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ mod project;
mod support_ticket;
mod transaction;
mod types;
mod validator;

#[derive(Clone)]
pub struct AppState {
Expand Down Expand Up @@ -53,6 +54,7 @@ pub fn api_router(app_state: AppState) -> Router {
.merge(support_ticket::router())
.merge(escrow::router())
.merge(newsletter::router())
.merge(validator::router())
.layer(trace_layer)
.layer(request_id_layer)
.layer(propagate_request_id_layer)
Expand Down Expand Up @@ -80,8 +82,5 @@ async fn shutdown_signal() {
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();

tokio::select! {
_ = ctrl_c => {},
_ = terminate => {},
}
tokio::select! {_ = ctrl_c => {}, _ = terminate => {},}
}
148 changes: 148 additions & 0 deletions src/http/validator/delete_profile.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
use crate::{
AppState, Error, Result,
http::validator::{DeleteValidatorProfileRequest, DeleteValidatorProfileResponse},
};
use axum::{Json, extract::State, http::StatusCode};
use garde::Validate;
use sqlx::PgPool;
use uuid::Uuid;

#[tracing::instrument(name = "Delete Validator Profile", skip(state, request))]
pub async fn delete_validator_profile(
State(state): State<AppState>,
Json(request): Json<DeleteValidatorProfileRequest>,
) -> Result<(StatusCode, Json<DeleteValidatorProfileResponse>)> {
request.validate()?;

tracing::info!(
wallet_address = %request.wallet_address,
"Attempting to delete validator profile"
);

// First, verify the validator profile exists
let validator =
get_validator_by_wallet_address(&state.db.pool, &request.wallet_address).await?;

if validator.is_none() {
tracing::warn!(
wallet_address = %request.wallet_address,
"Validator profile not found"
);
return Err(Error::NotFound);
}

let validator = validator.unwrap();
let deletion_time = chrono::Utc::now();

// Perform the deletion with all related data cleanup
delete_validator_and_related_data(&state.db.pool, &validator.id, &request.wallet_address)
.await?;

tracing::info!(
validator_id = %validator.id,
wallet_address = %request.wallet_address,
"Successfully deleted validator profile and all related data"
);

Ok((
StatusCode::OK,
Json(DeleteValidatorProfileResponse {
message: "Validator profile successfully deleted".to_string(),
validator_id: validator.id,
deleted_at: deletion_time,
}),
))
}

#[derive(Debug, sqlx::FromRow)]
struct ValidatorInfo {
id: Uuid,
}

async fn get_validator_by_wallet_address(
pool: &PgPool,
wallet_address: &str,
) -> Result<Option<ValidatorInfo>> {
let validator = sqlx::query_as::<_, ValidatorInfo>(
r#"
SELECT id
FROM validator_profiles
WHERE wallet_address = $1
"#,
)
.bind(wallet_address)
.fetch_optional(pool)
.await?;

Ok(validator)
}

async fn delete_validator_and_related_data(
pool: &PgPool,
validator_id: &Uuid,
wallet_address: &str,
) -> Result<()> {
let mut tx = pool.begin().await?;

// Delete from validator_expertise (many-to-many relationship)
let expertise_deleted = sqlx::query("DELETE FROM validator_expertise WHERE validator_id = $1")
.bind(validator_id)
.execute(&mut *tx)
.await?;

tracing::debug!(
validator_id = %validator_id,
expertise_rows_deleted = expertise_deleted.rows_affected(),
"Deleted validator expertise relationships"
);

// Delete from validator_programming_languages (many-to-many relationship)
let languages_deleted =
sqlx::query("DELETE FROM validator_programming_languages WHERE validator_id = $1")
.bind(validator_id)
.execute(&mut *tx)
.await?;

tracing::debug!(
validator_id = %validator_id,
language_rows_deleted = languages_deleted.rows_affected(),
"Deleted validator programming language relationships"
);

// Finally, delete the validator profile itself
let profile_deleted = sqlx::query("DELETE FROM validator_profiles WHERE id = $1")
.bind(validator_id)
.execute(&mut *tx)
.await?;

if profile_deleted.rows_affected() == 0 {
tracing::error!(
validator_id = %validator_id,
"Failed to delete validator profile - no rows affected"
);
return Err(Error::InternalServerError(anyhow::anyhow!(
"Failed to delete validator profile"
)));
}

// Also remove from escrow_users if they exist there
let escrow_deleted = sqlx::query("DELETE FROM escrow_users WHERE wallet_address = $1")
.bind(wallet_address)
.execute(&mut *tx)
.await?;

tracing::debug!(
validator_id = %validator_id,
escrow_deleted = escrow_deleted.rows_affected(),
"Deleted validator from escrow_users if present"
);

tx.commit().await?;

tracing::info!(
validator_id = %validator_id,
"Successfully committed validator profile deletion transaction"
);

Ok(())
}
27 changes: 27 additions & 0 deletions src/http/validator/domain.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use garde::Validate;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

#[derive(Debug, Serialize, Deserialize, Validate)]
pub struct DeleteValidatorProfileRequest {
#[garde(custom(validate_starknet_address))]
pub wallet_address: String,
}

#[derive(Debug, Serialize)]
pub struct DeleteValidatorProfileResponse {
pub message: String,
pub validator_id: Uuid,
pub deleted_at: chrono::DateTime<chrono::Utc>,
}

pub fn validate_starknet_address(address: &str, _context: &()) -> garde::Result {
if address.starts_with("0x")
&& address.len() == 66
&& address.chars().skip(2).all(|c| c.is_ascii_hexdigit())
{
Ok(())
} else {
Err(garde::Error::new("Invalid Starknet address"))
}
}
14 changes: 14 additions & 0 deletions src/http/validator/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
mod delete_profile;
mod domain;

use axum::{Router, routing::delete};
pub use domain::*;

use crate::AppState;

pub(crate) fn router() -> Router<AppState> {
Router::new().route(
"/validator/profile/delete",
delete(delete_profile::delete_validator_profile),
)
}
1 change: 1 addition & 0 deletions tests/api/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ mod newsletter;
mod projects;
mod support_tickets;
mod transaction;
mod validator;
Loading