diff --git a/Cargo.lock b/Cargo.lock index b166b9d40..643a18663 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4007,6 +4007,7 @@ dependencies = [ "hyper-tls", "ipnet", "jsonrpc", + "num_enum", "once_cell", "parquet", "parquet_derive", diff --git a/Cargo.toml b/Cargo.toml index 33780245b..ff65726e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ serde_json = "1.0" serde_piecewise_default = "0.2" serde-aux = "3.1" validator = { version = "0.16", features = ["derive"] } +num_enum = "0.7" # Storage aws-config = "0.56" diff --git a/integration/names.test.ts b/integration/names.test.ts index d19064841..c573d82a9 100644 --- a/integration/names.test.ts +++ b/integration/names.test.ts @@ -128,8 +128,10 @@ describe('Account profile names', () => { it('update name attributes', async () => { // Prepare updated attributes payload + const randomBioString = Array.from({ length: 24 }, + () => (Math.random().toString(36)[2] || '0')).join('') const updatedAttributes = { - bio: 'integration test domain updated attribute', + bio: randomBioString, }; const updateAttributesMessageObject = { attributes: updatedAttributes, @@ -152,7 +154,6 @@ describe('Account profile names', () => { ); expect(resp.status).toBe(200) - expect(resp.data.name).toBe(name) - expect(resp.data.attributes['bio']).toBe(updatedAttributes['bio']) + expect(resp.data['bio']).toBe(updatedAttributes['bio']) }) }) diff --git a/src/database/helpers.rs b/src/database/helpers.rs index 2b6014417..b50b75654 100644 --- a/src/database/helpers.rs +++ b/src/database/helpers.rs @@ -72,17 +72,27 @@ pub async fn update_name_attributes( name: String, attributes: HashMap, postgres: &PgPool, -) -> Result { - let insert_name_query = " +) -> Result, sqlx::error::Error> { + let update_attributes_query = " UPDATE names SET attributes = $2::hstore, updated_at = NOW() - WHERE name = $1 + WHERE name = $1 + RETURNING attributes::json "; - sqlx::query::(insert_name_query) + let result: (serde_json::Value,) = sqlx::query_as(update_attributes_query) .bind(&name) - // Convert JSON to String for hstore update .bind(&utils::hashmap_to_hstore(&attributes)) - .execute(postgres) - .await + .fetch_one(postgres) + .await?; + + let updated_attributes_result: Result, sqlx::Error> = + serde_json::from_value(result.0.clone()).map_err(|e| { + sqlx::Error::Protocol(format!( + "Failed to convert serde_json::Value to HashMap: {}", + e + )) + }); + + updated_attributes_result } #[instrument(skip(postgres))] diff --git a/src/handlers/profile/attributes.rs b/src/handlers/profile/attributes.rs index 7512cdd4b..b3007446b 100644 --- a/src/handlers/profile/attributes.rs +++ b/src/handlers/profile/attributes.rs @@ -2,6 +2,7 @@ use { super::{ super::HANDLER_TASK_METRICS, utils::{check_attributes, is_timestamp_within_interval}, + Eip155SupportedChains, RegisterRequest, UpdateAttributesPayload, UNIXTIMESTAMP_SYNC_THRESHOLD, @@ -18,7 +19,7 @@ use { Json, }, hyper::StatusCode, - sqlx::Error as SqlxError, + num_enum::TryFromPrimitive, std::{str::FromStr, sync::Arc}, tracing::log::{error, info}, wc::future::FutureExt, @@ -44,17 +45,17 @@ pub async fn handler_internal( let payload = match serde_json::from_str::(raw_payload) { Ok(payload) => payload, Err(e) => { - info!("Failed to deserialize register payload: {}", e); + info!("Failed to deserialize update attributes payload: {}", e); return Ok((StatusCode::BAD_REQUEST, "").into_response()); } }; // Check for the supported ENSIP-11 coin type - if request_payload.coin_type != 60 { + if Eip155SupportedChains::try_from_primitive(request_payload.coin_type).is_err() { info!("Unsupported coin type {}", request_payload.coin_type); return Ok(( StatusCode::BAD_REQUEST, - "Only Ethereum Mainnet (60) coin type is supported for name registration", + "Unsupported coin type for name attributes update", ) .into_response()); } @@ -107,7 +108,10 @@ pub async fn handler_internal( } // Check for the name address ownership and address from the signed payload - let name_owner = match name_addresses.addresses.get(&60) { + let name_owner = match name_addresses + .addresses + .get(&Eip155SupportedChains::Mainnet.into()) + { Some(address_entry) => match ethers::types::H160::from_str(&address_entry.address) { Ok(owner) => owner, Err(e) => { @@ -146,25 +150,11 @@ pub async fn handler_internal( .into_response()); } - let update_attributes_result = - update_name_attributes(name.clone(), payload.attributes, &state.postgres).await; - if let Err(e) = update_attributes_result { - error!("Failed to update attributes: {}", e); - return Ok((StatusCode::INTERNAL_SERVER_ERROR, "").into_response()); - } - - // Return the registered name, addresses and attributes - match get_name_and_addresses_by_name(name, &state.postgres.clone()).await { - Ok(response) => Ok(Json(response).into_response()), - Err(e) => match e { - SqlxError::RowNotFound => { - error!("New registered name is not found in the database: {}", e); - Ok((StatusCode::INTERNAL_SERVER_ERROR, "Name is not registered").into_response()) - } - _ => { - error!("Error on lookup new registered name: {}", e); - Ok((StatusCode::INTERNAL_SERVER_ERROR, "Name is not registered").into_response()) - } - }, + match update_name_attributes(name.clone(), payload.attributes, &state.postgres).await { + Err(e) => { + error!("Failed to update attributes: {}", e); + Ok((StatusCode::INTERNAL_SERVER_ERROR, "").into_response()) + } + Ok(attributes) => Ok(Json(attributes).into_response()), } } diff --git a/src/handlers/profile/mod.rs b/src/handlers/profile/mod.rs index 8b89893f7..4d17f004c 100644 --- a/src/handlers/profile/mod.rs +++ b/src/handlers/profile/mod.rs @@ -1,4 +1,5 @@ use { + num_enum::{IntoPrimitive, TryFromPrimitive}, once_cell::sync::Lazy, regex::Regex, serde::{Deserialize, Serialize}, @@ -11,6 +12,13 @@ pub mod register; pub mod reverse; pub mod utils; +/// List of supported Ethereum chains in ENSIP-11 format +#[repr(u32)] +#[derive(Debug, Clone, PartialEq, Eq, IntoPrimitive, TryFromPrimitive)] +enum Eip155SupportedChains { + Mainnet = 60, +} + pub const UNIXTIMESTAMP_SYNC_THRESHOLD: u64 = 10; /// Attributes value max length diff --git a/src/handlers/profile/register.rs b/src/handlers/profile/register.rs index ff0d6f332..1bc69df21 100644 --- a/src/handlers/profile/register.rs +++ b/src/handlers/profile/register.rs @@ -2,6 +2,7 @@ use { super::{ super::HANDLER_TASK_METRICS, utils::{check_attributes, is_timestamp_within_interval}, + Eip155SupportedChains, RegisterPayload, RegisterRequest, UNIXTIMESTAMP_SYNC_THRESHOLD, @@ -21,6 +22,7 @@ use { Json, }, hyper::StatusCode, + num_enum::TryFromPrimitive, sqlx::Error as SqlxError, std::{collections::HashMap, str::FromStr, sync::Arc}, tracing::log::{error, info}, @@ -51,11 +53,11 @@ pub async fn handler_internal( }; // Check for the supported ENSIP-11 coin type - if register_request.coin_type != 60 { + if Eip155SupportedChains::try_from_primitive(register_request.coin_type).is_err() { info!("Unsupported coin type {}", register_request.coin_type); return Ok(( StatusCode::BAD_REQUEST, - "Only Ethereum Mainnet (60) coin type is supported for name registration", + "Unsupported coin type for name registration", ) .into_response()); }