Register endpoint (#16)
All checks were successful
Rust Build / Check (push) Successful in 43s
Rust Build / Test Suite (push) Successful in 56s
Rust Build / Rustfmt (push) Successful in 27s
Rust Build / Clippy (push) Successful in 47s
Rust Build / build (push) Successful in 1m4s
Release Tagging / release (push) Successful in 31s
Rust Build / Check (pull_request) Successful in 43s
Rust Build / Test Suite (pull_request) Successful in 55s
Rust Build / Rustfmt (pull_request) Successful in 27s
Rust Build / Clippy (pull_request) Successful in 46s
Rust Build / build (pull_request) Successful in 1m6s
All checks were successful
Rust Build / Check (push) Successful in 43s
Rust Build / Test Suite (push) Successful in 56s
Rust Build / Rustfmt (push) Successful in 27s
Rust Build / Clippy (push) Successful in 47s
Rust Build / build (push) Successful in 1m4s
Release Tagging / release (push) Successful in 31s
Rust Build / Check (pull_request) Successful in 43s
Rust Build / Test Suite (pull_request) Successful in 55s
Rust Build / Rustfmt (pull_request) Successful in 27s
Rust Build / Clippy (pull_request) Successful in 46s
Rust Build / build (pull_request) Successful in 1m6s
Reviewed-on: #16 Co-authored-by: phoenix <kundeng94@gmail.com> Co-committed-by: phoenix <kundeng94@gmail.com>
This commit is contained in:
@@ -19,7 +19,7 @@ jobs:
|
|||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: 1.85.0
|
toolchain: 1.86.0
|
||||||
components: cargo
|
components: cargo
|
||||||
|
|
||||||
- name: Extract Version from Cargo.toml
|
- name: Extract Version from Cargo.toml
|
||||||
|
@@ -18,7 +18,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: 1.85.0
|
toolchain: 1.86.0
|
||||||
- run: |
|
- run: |
|
||||||
mkdir -p ~/.ssh
|
mkdir -p ~/.ssh
|
||||||
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/icarus_models_deploy_key
|
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/icarus_models_deploy_key
|
||||||
@@ -53,7 +53,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: 1.85.0
|
toolchain: 1.86.0
|
||||||
# --- Add this step for explicit verification ---
|
# --- Add this step for explicit verification ---
|
||||||
- name: Verify Docker Environment
|
- name: Verify Docker Environment
|
||||||
run: |
|
run: |
|
||||||
@@ -68,7 +68,6 @@ jobs:
|
|||||||
echo "Docker environment check complete."
|
echo "Docker environment check complete."
|
||||||
# NOTE: Do NOT use continue-on-error here.
|
# NOTE: Do NOT use continue-on-error here.
|
||||||
# If Docker isn't working as expected, the job SHOULD fail here.
|
# If Docker isn't working as expected, the job SHOULD fail here.
|
||||||
# --- Optional but Recommended: Database Migrations Step ---
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
env:
|
env:
|
||||||
# Define DATABASE_URL for tests to use
|
# Define DATABASE_URL for tests to use
|
||||||
@@ -94,7 +93,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: 1.85.0
|
toolchain: 1.86.0
|
||||||
- run: rustup component add rustfmt
|
- run: rustup component add rustfmt
|
||||||
- run: |
|
- run: |
|
||||||
mkdir -p ~/.ssh
|
mkdir -p ~/.ssh
|
||||||
@@ -113,7 +112,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: 1.85.0
|
toolchain: 1.86.0
|
||||||
- run: rustup component add clippy
|
- run: rustup component add clippy
|
||||||
- run: |
|
- run: |
|
||||||
mkdir -p ~/.ssh
|
mkdir -p ~/.ssh
|
||||||
@@ -132,7 +131,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: 1.85.0
|
toolchain: 1.86.0
|
||||||
- run: |
|
- run: |
|
||||||
mkdir -p ~/.ssh
|
mkdir -p ~/.ssh
|
||||||
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/icarus_models_deploy_key
|
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/icarus_models_deploy_key
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "icarus_auth"
|
name = "icarus_auth"
|
||||||
version = "0.1.1"
|
version = "0.2.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
rust-version = "1.86"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { version = "0.8.3" }
|
axum = { version = "0.8.3" }
|
||||||
@@ -11,12 +12,13 @@ tokio = { version = "1.44.1", features = ["rt-multi-thread"] }
|
|||||||
tracing-subscriber = { version = "0.3.19" }
|
tracing-subscriber = { version = "0.3.19" }
|
||||||
tower = { version = "0.5.2" }
|
tower = { version = "0.5.2" }
|
||||||
hyper = { version = "1.6.0" }
|
hyper = { version = "1.6.0" }
|
||||||
sqlx = { version = "0.8.3", features = ["postgres", "runtime-tokio-native-tls", "uuid"] }
|
sqlx = { version = "0.8.3", features = ["postgres", "runtime-tokio-native-tls", "time", "uuid"] }
|
||||||
dotenvy = { version = "0.15.7" }
|
dotenvy = { version = "0.15.7" }
|
||||||
uuid = { version = "1.16.0", features = ["v4", "serde"] }
|
uuid = { version = "1.16.0", features = ["v4", "serde"] }
|
||||||
argon2 = { version = "0.5.3", features = ["std"] } # Use the latest 0.5.x version
|
argon2 = { version = "0.5.3", features = ["std"] } # Use the latest 0.5.x version
|
||||||
rand = { version = "0.9" }
|
rand = { version = "0.9" }
|
||||||
icarus_models = { git = "ssh://git@git.kundeng.us/phoenix/icarus_models.git", tag = "v0.3.0" }
|
time = { version = "0.3.41", features = ["macros", "serde"] }
|
||||||
|
icarus_models = { git = "ssh://git@git.kundeng.us/phoenix/icarus_models.git", tag = "v0.4.0" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
http-body-util = { version = "0.1.3" }
|
http-body-util = { version = "0.1.3" }
|
||||||
|
@@ -5,5 +5,18 @@ CREATE TABLE IF NOT EXISTS "user" (
|
|||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
username TEXT NOT NULL,
|
username TEXT NOT NULL,
|
||||||
password TEXT NOT NULL,
|
password TEXT NOT NULL,
|
||||||
date_created TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
email TEXT NOT NULL,
|
||||||
|
phone TEXT NOT NULL,
|
||||||
|
firstname TEXT NOT NULL,
|
||||||
|
lastname TEXT NOT NULL,
|
||||||
|
email_verified BOOL NOT NULL,
|
||||||
|
date_created TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
status TEXT NOT NULL,
|
||||||
|
last_login TIMESTAMPTZ NULL DEFAULT NOW(),
|
||||||
|
salt_id UUID NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS "salt" (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
salt TEXT NOT NULL
|
||||||
);
|
);
|
||||||
|
@@ -1,46 +1,104 @@
|
|||||||
use axum::{Json, http::StatusCode};
|
use axum::{Json, http::StatusCode};
|
||||||
|
|
||||||
use crate::models;
|
use crate::hashing;
|
||||||
use crate::repo;
|
use crate::repo;
|
||||||
|
|
||||||
mod response {
|
pub mod request {
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::models;
|
#[derive(Default, Deserialize, Serialize)]
|
||||||
|
pub struct Request {
|
||||||
|
#[serde(skip_serializing_if = "String::is_empty")]
|
||||||
|
pub username: String,
|
||||||
|
#[serde(skip_serializing_if = "String::is_empty")]
|
||||||
|
pub password: String,
|
||||||
|
#[serde(skip_serializing_if = "String::is_empty")]
|
||||||
|
pub email: String,
|
||||||
|
#[serde(skip_serializing_if = "String::is_empty")]
|
||||||
|
pub phone: String,
|
||||||
|
#[serde(skip_serializing_if = "String::is_empty")]
|
||||||
|
pub firstname: String,
|
||||||
|
#[serde(skip_serializing_if = "String::is_empty")]
|
||||||
|
pub lastname: String,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod response {
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
pub struct Response {
|
pub struct Response {
|
||||||
pub message: String,
|
pub message: String,
|
||||||
pub data: models::common::User,
|
pub data: Vec<icarus_models::user::User>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn register_user(
|
pub async fn register_user(
|
||||||
axum::Extension(pool): axum::Extension<sqlx::PgPool>,
|
axum::Extension(pool): axum::Extension<sqlx::PgPool>,
|
||||||
Json(payload): Json<models::common::CreateUser>,
|
Json(payload): Json<request::Request>,
|
||||||
) -> (StatusCode, Json<response::Response>) {
|
) -> (StatusCode, Json<response::Response>) {
|
||||||
let mut user = models::common::User {
|
let mut user = icarus_models::user::User {
|
||||||
id: uuid::Uuid::nil(),
|
id: uuid::Uuid::nil(),
|
||||||
username: payload.username.clone(),
|
username: payload.username.clone(),
|
||||||
password: payload.password.clone(),
|
password: payload.password.clone(),
|
||||||
|
email: payload.email.clone(),
|
||||||
|
phone: payload.phone.clone(),
|
||||||
|
firstname: payload.firstname.clone(),
|
||||||
|
lastname: payload.lastname.clone(),
|
||||||
|
status: String::from("Active"),
|
||||||
|
email_verified: true,
|
||||||
|
date_created: Some(time::OffsetDateTime::now_utc()),
|
||||||
|
last_login: None,
|
||||||
|
salt_id: uuid::Uuid::nil(),
|
||||||
};
|
};
|
||||||
|
|
||||||
match repo::user::insert(&pool, &user).await {
|
match repo::user::exists(&pool, &user.username).await {
|
||||||
Ok(id) => {
|
Ok(res) => {
|
||||||
user.id = id;
|
if res {
|
||||||
(
|
(
|
||||||
StatusCode::CREATED,
|
StatusCode::NOT_FOUND,
|
||||||
Json(response::Response {
|
Json(response::Response {
|
||||||
message: String::from("User inserted"),
|
message: String::from("Error"),
|
||||||
data: user,
|
data: vec![user],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
let salt_string = hashing::generate_salt().unwrap();
|
||||||
|
let mut salt = icarus_models::user::salt::Salt::default();
|
||||||
|
let generated_salt = salt_string;
|
||||||
|
salt.salt = generated_salt.to_string();
|
||||||
|
salt.id = repo::salt::insert(&pool, &salt).await.unwrap();
|
||||||
|
user.salt_id = salt.id;
|
||||||
|
let hashed_password =
|
||||||
|
hashing::hash_password(&user.password, &generated_salt).unwrap();
|
||||||
|
user.password = hashed_password;
|
||||||
|
|
||||||
|
match repo::user::insert(&pool, &user).await {
|
||||||
|
Ok(id) => {
|
||||||
|
user.id = id;
|
||||||
|
(
|
||||||
|
StatusCode::CREATED,
|
||||||
|
Json(response::Response {
|
||||||
|
message: String::from("User created"),
|
||||||
|
data: vec![user],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Err(err) => (
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
Json(response::Response {
|
||||||
|
message: err.to_string(),
|
||||||
|
data: vec![user],
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(err) => (
|
Err(err) => (
|
||||||
StatusCode::BAD_REQUEST,
|
StatusCode::BAD_REQUEST,
|
||||||
Json(response::Response {
|
Json(response::Response {
|
||||||
message: err.to_string(),
|
message: err.to_string(),
|
||||||
data: user,
|
data: vec![user],
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
@@ -8,12 +8,18 @@ use argon2::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn hash_password(password: &String) -> Result<String, argon2::password_hash::Error> {
|
pub fn generate_salt() -> Result<SaltString, argon2::Error> {
|
||||||
let password_bytes = password.as_bytes();
|
|
||||||
|
|
||||||
// Generate a random salt
|
// Generate a random salt
|
||||||
// SaltString::generate uses OsRng internally for cryptographic security
|
// SaltString::generate uses OsRng internally for cryptographic security
|
||||||
let salt = SaltString::generate(&mut OsRng);
|
let salt = SaltString::generate(&mut OsRng);
|
||||||
|
Ok(salt)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hash_password(
|
||||||
|
password: &String,
|
||||||
|
salt: &SaltString,
|
||||||
|
) -> Result<String, argon2::password_hash::Error> {
|
||||||
|
let password_bytes = password.as_bytes();
|
||||||
|
|
||||||
// Create an Argon2 instance with default parameters (recommended)
|
// Create an Argon2 instance with default parameters (recommended)
|
||||||
// You could customize parameters here if needed, but defaults are strong
|
// You could customize parameters here if needed, but defaults are strong
|
||||||
@@ -22,7 +28,7 @@ pub fn hash_password(password: &String) -> Result<String, argon2::password_hash:
|
|||||||
// Hash the password with the salt
|
// Hash the password with the salt
|
||||||
// The output is a PasswordHash string format that includes algorithm, version,
|
// The output is a PasswordHash string format that includes algorithm, version,
|
||||||
// parameters, salt, and the hash itself.
|
// parameters, salt, and the hash itself.
|
||||||
let password_hash = argon2.hash_password(password_bytes, &salt)?.to_string();
|
let password_hash = argon2.hash_password(password_bytes, salt)?.to_string();
|
||||||
|
|
||||||
Ok(password_hash)
|
Ok(password_hash)
|
||||||
}
|
}
|
||||||
@@ -56,7 +62,8 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_hash_password() {
|
fn test_hash_password() {
|
||||||
let some_password = String::from("somethingrandom");
|
let some_password = String::from("somethingrandom");
|
||||||
match hash_password(&some_password) {
|
let salt = generate_salt().unwrap();
|
||||||
|
match hash_password(&some_password, &salt) {
|
||||||
Ok(p) => match verify_password(&some_password, p.clone()) {
|
Ok(p) => match verify_password(&some_password, p.clone()) {
|
||||||
Ok(res) => {
|
Ok(res) => {
|
||||||
assert_eq!(res, true);
|
assert_eq!(res, true);
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
pub mod callers;
|
pub mod callers;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod hashing;
|
pub mod hashing;
|
||||||
pub mod models;
|
|
||||||
pub mod repo;
|
pub mod repo;
|
||||||
|
|
||||||
pub mod keys {
|
pub mod keys {
|
||||||
|
34
src/main.rs
34
src/main.rs
@@ -65,16 +65,9 @@ mod tests {
|
|||||||
http::{Request, StatusCode},
|
http::{Request, StatusCode},
|
||||||
};
|
};
|
||||||
use http_body_util::BodyExt;
|
use http_body_util::BodyExt;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use tower::ServiceExt; // for `call`, `oneshot`, and `ready`
|
use tower::ServiceExt; // for `call`, `oneshot`, and `ready`
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
|
||||||
struct Response {
|
|
||||||
pub message: String,
|
|
||||||
pub data: icarus_auth::models::common::User,
|
|
||||||
}
|
|
||||||
|
|
||||||
mod db_mgr {
|
mod db_mgr {
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
@@ -191,14 +184,22 @@ mod tests {
|
|||||||
|
|
||||||
let app = init::routes().await.layer(axum::Extension(pool));
|
let app = init::routes().await.layer(axum::Extension(pool));
|
||||||
|
|
||||||
let usr = icarus_auth::models::common::CreateUser {
|
let usr = icarus_auth::callers::register::request::Request {
|
||||||
username: String::from("somethingsss"),
|
username: String::from("somethingsss"),
|
||||||
password: String::from("Raindown!"),
|
password: String::from("Raindown!"),
|
||||||
|
email: String::from("dev@null.com"),
|
||||||
|
phone: String::from("1234567890"),
|
||||||
|
firstname: String::from("Bob"),
|
||||||
|
lastname: String::from("Smith"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let payload = json!({
|
let payload = json!({
|
||||||
"username": &usr.username,
|
"username": &usr.username,
|
||||||
"password": &usr.password,
|
"password": &usr.password,
|
||||||
|
"email": &usr.email,
|
||||||
|
"phone": &usr.phone,
|
||||||
|
"firstname": &usr.firstname,
|
||||||
|
"lastname": &usr.lastname,
|
||||||
});
|
});
|
||||||
|
|
||||||
let response = app
|
let response = app
|
||||||
@@ -214,16 +215,27 @@ mod tests {
|
|||||||
|
|
||||||
match response {
|
match response {
|
||||||
Ok(resp) => {
|
Ok(resp) => {
|
||||||
assert_eq!(resp.status(), StatusCode::CREATED, "Message: {:?}", resp);
|
assert_eq!(
|
||||||
|
resp.status(),
|
||||||
|
StatusCode::CREATED,
|
||||||
|
"Message: {:?} {:?}",
|
||||||
|
resp,
|
||||||
|
usr.username
|
||||||
|
);
|
||||||
let body = axum::body::to_bytes(resp.into_body(), usize::MAX)
|
let body = axum::body::to_bytes(resp.into_body(), usize::MAX)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let parsed_body: Response = serde_json::from_slice(&body).unwrap();
|
let parsed_body: callers::register::response::Response =
|
||||||
|
serde_json::from_slice(&body).unwrap();
|
||||||
|
let returned_usr = &parsed_body.data[0];
|
||||||
|
|
||||||
|
assert_eq!(false, returned_usr.id.is_nil(), "Id is not populated");
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
usr.username, parsed_body.data.username,
|
usr.username, returned_usr.username,
|
||||||
"Usernames do not match"
|
"Usernames do not match"
|
||||||
);
|
);
|
||||||
|
assert!(returned_usr.date_created.is_some(), "Date Created is empty");
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
assert!(false, "Error: {:?}", err.to_string());
|
assert!(false, "Error: {:?}", err.to_string());
|
||||||
|
@@ -1,14 +0,0 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
|
||||||
pub struct CreateUser {
|
|
||||||
pub username: String,
|
|
||||||
pub password: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
|
||||||
pub struct User {
|
|
||||||
pub id: uuid::Uuid,
|
|
||||||
pub username: String,
|
|
||||||
pub password: String,
|
|
||||||
}
|
|
@@ -1 +0,0 @@
|
|||||||
pub mod common;
|
|
106
src/repo/mod.rs
106
src/repo/mod.rs
@@ -1,20 +1,104 @@
|
|||||||
pub mod user {
|
pub mod user {
|
||||||
use crate::models;
|
use sqlx::Row;
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Serialize, sqlx::FromRow)]
|
||||||
|
pub struct InsertedData {
|
||||||
|
pub id: uuid::Uuid,
|
||||||
|
pub date_created: Option<time::OffsetDateTime>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn exists(pool: &sqlx::PgPool, username: &String) -> Result<bool, sqlx::Error> {
|
||||||
|
let result = sqlx::query(
|
||||||
|
r#"
|
||||||
|
SELECT 1 FROM "user" WHERE username = $1
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(username)
|
||||||
|
.fetch_optional(pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(r) => Ok(r.is_some()),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn insert(
|
pub async fn insert(
|
||||||
pool: &sqlx::PgPool,
|
pool: &sqlx::PgPool,
|
||||||
user: &models::common::User,
|
user: &icarus_models::user::User,
|
||||||
) -> Result<uuid::Uuid, sqlx::Error> {
|
) -> Result<uuid::Uuid, sqlx::Error> {
|
||||||
let insert_sql = "INSERT INTO \"user\" (username, password) VALUES ($1, $2) RETURNING id";
|
let row = sqlx::query(
|
||||||
|
r#"
|
||||||
match sqlx::query_scalar(insert_sql)
|
INSERT INTO "user" (username, password, email, phone, firstname, lastname, email_verified, status, salt_id)
|
||||||
.bind(&user.username) // Bind the input message securely
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||||
|
RETURNING id, date_created;
|
||||||
|
"#)
|
||||||
|
.bind(&user.username)
|
||||||
.bind(&user.password)
|
.bind(&user.password)
|
||||||
.fetch_one(pool) // Execute and expect exactly ONE row with ONE column back
|
.bind(&user.email)
|
||||||
.await
|
.bind(&user.phone)
|
||||||
{
|
.bind(&user.firstname)
|
||||||
Ok(o) => Ok(o),
|
.bind(&user.lastname)
|
||||||
Err(err) => Err(err), // _ => uuid::Uuid::nil(),
|
.bind(user.email_verified)
|
||||||
|
.bind(&user.status)
|
||||||
|
.bind(user.salt_id)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
eprintln!("Error inserting item: {}", e);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let result = InsertedData {
|
||||||
|
id: row.try_get("id").map_err(|_e| sqlx::Error::RowNotFound)?,
|
||||||
|
date_created: row
|
||||||
|
.try_get("date_created")
|
||||||
|
.map_err(|_e| sqlx::Error::RowNotFound)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
if !result.id.is_nil() {
|
||||||
|
Ok(result.id)
|
||||||
|
} else {
|
||||||
|
Err(sqlx::Error::RowNotFound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod salt {
|
||||||
|
use sqlx::Row;
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Serialize, sqlx::FromRow)]
|
||||||
|
pub struct InsertedData {
|
||||||
|
pub id: uuid::Uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn insert(
|
||||||
|
pool: &sqlx::PgPool,
|
||||||
|
salt: &icarus_models::user::salt::Salt,
|
||||||
|
) -> Result<uuid::Uuid, sqlx::Error> {
|
||||||
|
let row = sqlx::query(
|
||||||
|
r#"
|
||||||
|
INSERT INTO "salt" (salt)
|
||||||
|
VALUES ($1)
|
||||||
|
RETURNING id;
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(&salt.salt)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
eprintln!("Error inserting item: {}", e);
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let result = InsertedData {
|
||||||
|
id: row.try_get("id").map_err(|_e| sqlx::Error::RowNotFound)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
if !result.id.is_nil() {
|
||||||
|
Ok(result.id)
|
||||||
|
} else {
|
||||||
|
Err(sqlx::Error::RowNotFound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user