use serde::{Deserialize, Serialize};
use uuid::Uuid;
use anyhow::Result;
use sqlx::postgres::PgRow;
use sqlx::{Done, FromRow, PgPool, Row};
#[derive(Serialize, FromRow, Debug)]
pub struct Person {
pub id: Uuid,
pub name: String,
pub email: String,
pub pass: String,
}
#[allow(unused_variables)]
pub trait PersonHook {
fn validate(&self, person: &PersonRequest, action: &str) -> Result<()>;
fn prepare(&self, person: &mut PersonRequest, action: &str) -> Result<()>;
fn prepare_id(&self, id: &mut Uuid, action: &str) -> Result<()>;
fn processed(&self, person: &mut Person, action: &str) -> Result<()>;
}
pub struct PersonHooks {
hooks: Vec<Box<dyn PersonHook + Send>>,
}
impl PersonHooks {
pub fn initialize() -> Self {
Self { hooks: Vec::new() }
}
pub fn register_hook<H: PersonHook + 'static + Send>(&mut self, hook: H) {
self.hooks.push(Box::new(hook));
}
}
#[derive(Deserialize, Serialize, Debug)]
pub struct PersonRequest {
pub name: String,
pub email: String,
pub pass: String,
}
impl Person {
pub async fn create(
person_request: PersonRequest,
db: &PgPool,
hooks: &PersonHooks,
) -> Result<Person> {
let action = "create";
for hook in &hooks.hooks {
hook.validate(&person_request, action)?;
}
let mut request = person_request;
for hook in &hooks.hooks {
hook.prepare(&mut request, action)?;
}
let mut transaction = db.begin().await?;
let mut person = sqlx::query("INSERT INTO person (name, email, pass) VALUES ($1, $2, $3) RETURNING id, name, email, pass")
.bind(&request.name)
.bind(&request.email)
.bind(&request.pass)
.map(|row: PgRow| {
Person {
id: row.get(0),
name: row.get(1),
email: row.get(2),
pass: row.get(3)
}
})
.fetch_one(&mut transaction)
.await?;
transaction.commit().await?;
for hook in &hooks.hooks {
hook.processed(&mut person, action)?;
}
Ok(person)
}
pub async fn read(uuid: Option<Uuid>, db: &PgPool, hooks: &PersonHooks) -> Result<Vec<Person>> {
let action = "read";
if let Some(mut id) = uuid {
for hook in &hooks.hooks {
hook.prepare_id(&mut id, action)?;
}
}
let records = if let Some(id) = uuid {
sqlx::query(
r#"
SELECT id, name, email, pass
FROM person
WHERE id = $1
"#,
)
.bind(id)
.map(|row: PgRow| Person {
id: row.get(0),
name: row.get(1),
email: row.get(2),
pass: row.get(3),
})
.fetch_all(db)
.await?
} else {
sqlx::query(
r#"
SELECT id, name, email, pass
FROM person
"#,
)
.map(|row: PgRow| Person {
id: row.get(0),
name: row.get(1),
email: row.get(2),
pass: row.get(3),
})
.fetch_all(db)
.await?
};
let mut persons = vec![];
for record in records {
persons.push(Person {
id: record.id,
name: record.name,
email: record.email,
pass: record.pass,
});
}
Ok(persons)
}
pub async fn update(
id: Uuid,
person_request: PersonRequest,
db: &PgPool,
hooks: &PersonHooks,
) -> Result<Person> {
let action = "update";
for hook in &hooks.hooks {
hook.validate(&person_request, action)?;
}
let mut request = person_request;
if !request.pass.is_empty() {
for hook in &hooks.hooks {
hook.prepare(&mut request, action)?;
}
}
let mut transaction = db.begin().await.unwrap();
let mut person = sqlx::query(
r#"
UPDATE person SET name = $1, email = $2
WHERE id = $3
RETURNING id, name, email, pass
"#,
)
.bind(&request.name)
.bind(&request.email)
.bind(id)
.map(|row: PgRow| Person {
id: row.get(0),
name: row.get(1),
email: row.get(2),
pass: row.get(3),
})
.fetch_one(&mut transaction)
.await?;
transaction.commit().await.unwrap();
for hook in &hooks.hooks {
hook.processed(&mut person, action)?;
}
Ok(person)
}
pub async fn delete(mut id: Uuid, db: &PgPool, hooks: &PersonHooks) -> Result<u64> {
let action = "delete";
for hook in &hooks.hooks {
hook.prepare_id(&mut id, action)?;
}
let mut transaction = db.begin().await?;
let deleted = sqlx::query("DELETE FROM person WHERE id = $1")
.bind(id)
.execute(&mut transaction)
.await?
.rows_affected();
transaction.commit().await?;
Ok(deleted)
}
}