Skip to content
Open
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
41 changes: 41 additions & 0 deletions migrations/20250718172039_validators.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
-- Add migration script here

-- validators table
CREATE TABLE IF NOT EXISTS validators (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
wallet_address TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
bio TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- programming languages master list
CREATE TABLE IF NOT EXISTS programming_languages (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL UNIQUE
);

-- expertise areas master list
CREATE TABLE IF NOT EXISTS expertise_areas (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL UNIQUE
);

-- many‑to‑many from validators → languages
CREATE TABLE IF NOT EXISTS validator_programming_languages (
validator_id UUID NOT NULL REFERENCES validators(id) ON DELETE CASCADE,
language_id UUID NOT NULL REFERENCES programming_languages(id) ON DELETE CASCADE,
PRIMARY KEY (validator_id, language_id)
);

-- many‑to‑many from validators → expertise
CREATE TABLE IF NOT EXISTS validator_expertise_areas (
validator_id UUID NOT NULL REFERENCES validators(id) ON DELETE CASCADE,
expertise_id UUID NOT NULL REFERENCES expertise_areas(id) ON DELETE CASCADE,
PRIMARY KEY (validator_id, expertise_id)
);

-- index for fast wallet lookups
CREATE INDEX IF NOT EXISTS idx_validators_wallet
ON validators(wallet_address);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @PoulavBhowmick03 after going through the setup migration, anything particularly wrong with the validators table/schema in the migration script that spurred you to go ahead and create yours?

2 changes: 1 addition & 1 deletion src/http/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ mod helpers;
mod projects;
mod support_tickets;
mod types;

mod validators;
#[derive(Clone)]
pub struct AppState {
pub db: Db,
Expand Down
21 changes: 21 additions & 0 deletions src/http/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,24 @@ pub struct AllocateBountyRequest {
pub currency: String,
pub bounty_expiry_date: Option<DateTime<Utc>>, // ISO8601 string
}

#[derive(Debug, Serialize, Deserialize)]
pub struct RegisterValidatorParams {
pub wallet_address: String,
pub name: String,
pub bio: String,
pub programming_lang: Vec<String>,
pub expertise_area: Vec<String>,
}

#[derive(Debug, Serialize)]
pub struct ValidatorProfile {
pub validator_id: Uuid,
pub wallet_address: String,
pub name: String,
pub bio: Option<String>,
pub programming_languages: Vec<String>,
pub expertise_areas: Vec<String>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
123 changes: 123 additions & 0 deletions src/http/validators.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
use axum::{
Router,
extract::{Json, State},
http::StatusCode,
routing::post,
};
use sqlx::{Executor, Postgres};

use super::types::{RegisterValidatorParams, ValidatorProfile};
use crate::AppState;

pub(crate) fn router() -> Router<AppState> {
Router::new().route("/validators", post(register_validator))
}

async fn register_validator(
State(state): State<AppState>,
Json(payload): Json<RegisterValidatorParams>,
) -> Result<(StatusCode, Json<ValidatorProfile>), StatusCode> {
let mut tx = state
.db
.pool
.begin()
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

// 2) insert validator (UUID PK)
let row = sqlx::query!(

Check failure on line 28 in src/http/validators.rs

View workflow job for this annotation

GitHub Actions / test

cannot find type `Uuid` in this scope

Check failure on line 28 in src/http/validators.rs

View workflow job for this annotation

GitHub Actions / Clippy

`SQLX_OFFLINE=true` but there is no cached data for this query, run `cargo sqlx prepare` to update the query cache or unset `SQLX_OFFLINE`
r#"
INSERT INTO validators(wallet_address, name, bio)
VALUES ($1, $2, $3)
ON CONFLICT(wallet_address) DO NOTHING
RETURNING id AS "validator_id!: Uuid",
wallet_address,
name,
bio,
created_at,
updated_at
"#,
payload.wallet_address,
payload.name,
payload.bio,
)
.fetch_optional(&mut *tx)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
.ok_or(StatusCode::CONFLICT)?; // 409 on duplicate

// helper: upsert into a text‑lookup table, returning its numeric ID
async fn upsert_and_get_id(
executor: impl Executor<'_, Database = Postgres>, // Generic executor
table: &str,
value: &str,
) -> Result<i32, sqlx::Error> {
let sql = format!(
"INSERT INTO {table} (value) VALUES ($1) ON CONFLICT (value) DO UPDATE SET value = EXCLUDED.value RETURNING id",
table = table
);
sqlx::query_scalar(&sql)
.bind(value)
.fetch_one(executor)
.await
}

// 3) languages (note: payload.programming_languages)
for lang in &payload.programming_lang {
let lang_id: i32 = upsert_and_get_id(&mut *tx, "programming_languages", lang)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
sqlx::query!(

Check failure on line 70 in src/http/validators.rs

View workflow job for this annotation

GitHub Actions / test

error returned from database: relation "validator_programming_lang" does not exist

Check failure on line 70 in src/http/validators.rs

View workflow job for this annotation

GitHub Actions / Clippy

`SQLX_OFFLINE=true` but there is no cached data for this query, run `cargo sqlx prepare` to update the query cache or unset `SQLX_OFFLINE`
r#"
INSERT INTO validator_programming_lang
(validator_id, language_id)
VALUES ($1, $2)
ON CONFLICT DO NOTHING
"#,
row.validator_id,
lang_id,
)
.execute(&mut tx)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
}

// 4) expertise areas (note: payload.expertise_areas)
for area in &payload.expertise_area {
let exp_id: i32 = upsert_and_get_id(&mut *tx, "expertise_areas", area)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
sqlx::query!(

Check failure on line 90 in src/http/validators.rs

View workflow job for this annotation

GitHub Actions / test

error returned from database: relation "validator_expertise_area" does not exist

Check failure on line 90 in src/http/validators.rs

View workflow job for this annotation

GitHub Actions / Clippy

`SQLX_OFFLINE=true` but there is no cached data for this query, run `cargo sqlx prepare` to update the query cache or unset `SQLX_OFFLINE`
r#"
INSERT INTO validator_expertise_area
(validator_id, expertise_id)
VALUES ($1, $2)
ON CONFLICT DO NOTHING
"#,
row.validator_id,
exp_id,
)
.execute(&mut tx)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
}

// 5) commit
tx.commit()
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

// 6) build our JSON response
let profile = ValidatorProfile {
validator_id: row.validator_id,
wallet_address: row.wallet_address,
name: row.name,
bio: row.bio,
programming_languages: payload.programming_lang.clone(),
expertise_areas: payload.expertise_area.clone(),
created_at: row.created_at,
updated_at: row.updated_at,
};

Ok((StatusCode::CREATED, Json(profile)))
}
Loading