portfu_admin 1.3.3

Library of Admin tools build on toip of Portfu
Documentation
use crate::stores::DataStoreEntry;
#[cfg(feature = "postgres")]
use crate::stores::DatabaseEntry;
use portfu::prelude::uuid::Uuid;
use serde::{Deserialize, Serialize};
#[cfg(feature = "postgres")]
use sqlx::postgres::PgRow;
#[cfg(feature = "postgres")]
use sqlx::query::Query;
#[cfg(feature = "postgres")]
use sqlx::{Error, FromRow, Postgres, Row};
use std::fmt::{Display, Formatter};
use std::str::FromStr;
use struct_field_names_as_array::FieldNamesAsSlice;
use time::OffsetDateTime;

#[derive(FieldNamesAsSlice, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct User {
    pub id: i64,
    pub uuid: Uuid,
    pub username: String,
    pub email: String,
    pub role: UserRole,
    pub first_name: String,
    pub last_name: String,
    pub home_phone: String,
    pub work_phone: String,
    pub cell_phone: String,
    pub address: String,
    pub address2: String,
    pub city: String,
    pub state: String,
    pub country: String,
    pub notes: String,
    pub created: OffsetDateTime,
    pub updated: OffsetDateTime,
}
impl Default for User {
    fn default() -> Self {
        Self {
            id: -1,
            uuid: Default::default(),
            username: Default::default(),
            email: Default::default(),
            role: Default::default(),
            first_name: Default::default(),
            last_name: Default::default(),
            home_phone: Default::default(),
            work_phone: Default::default(),
            cell_phone: Default::default(),
            address: Default::default(),
            address2: Default::default(),
            city: Default::default(),
            state: Default::default(),
            country: Default::default(),
            notes: Default::default(),
            created: OffsetDateTime::now_utc(),
            updated: OffsetDateTime::now_utc(),
        }
    }
}
impl DataStoreEntry<i64> for User {
    fn key_name() -> &'static str {
        "id"
    }
    fn key_value(&self) -> i64 {
        self.id
    }

    fn parameters() -> &'static [&'static str] {
        User::FIELD_NAMES_AS_SLICE
    }

    fn matches(&self, name: &str, other: &str) -> bool {
        if !Self::FIELD_NAMES_AS_SLICE.contains(&name) {
            return false;
        }
        match name {
            "id" => self.id.to_string() == other,
            "uuid" => self.uuid.to_string() == other,
            "username" => self.username == other,
            "email" => self.email == other,
            "role" => self.role.to_string().eq_ignore_ascii_case(other),
            "first_name" => self.first_name == other,
            "last_name" => self.last_name == other,
            "home_phone" => self.home_phone == other,
            "work_phone" => self.work_phone == other,
            "cell_phone" => self.cell_phone == other,
            "address" => self.address == other,
            "address2" => self.address2 == other,
            "city" => self.city == other,
            "state" => self.state == other,
            "country" => self.country == other,
            _ => false,
        }
    }
}

#[cfg(feature = "postgres")]
impl<'r> FromRow<'r, PgRow> for User {
    fn from_row(row: &'r PgRow) -> Result<Self, Error> {
        Ok(Self {
            id: row.try_get("id")?,
            uuid: Uuid::parse_str(row.try_get("uuid")?).map_err(|e| Error::Decode(e.into()))?,
            username: row.try_get("username")?,
            email: row.try_get("email")?,
            role: row.try_get("role")?,
            first_name: row.try_get("first_name")?,
            last_name: row.try_get("last_name:")?,
            home_phone: row.try_get("home_phone")?,
            work_phone: row.try_get("work_phone")?,
            cell_phone: row.try_get("cell_phone")?,
            address: row.try_get("address")?,
            address2: row.try_get("address2")?,
            city: row.try_get("city")?,
            state: row.try_get("state")?,
            country: row.try_get("country")?,
            notes: row.try_get("notes")?,
            created: row.try_get("created")?,
            updated: row.try_get("updated")?,
        })
    }
}

#[cfg(feature = "postgres")]
impl DatabaseEntry<Postgres, PgRow, i64> for User {
    fn bind<'q>(
        &'q self,
        mut query: Query<'q, Postgres, <Postgres as sqlx::Database>::Arguments<'q>>,
        field: &str,
    ) -> Query<'q, Postgres, <Postgres as sqlx::Database>::Arguments<'q>> {
        if !Self::FIELD_NAMES_AS_SLICE.contains(&field) {
            return query;
        }
        query = match field {
            "id" => query.bind(self.id),
            "uuid" => query.bind(self.uuid.to_string()),
            "username" => query.bind(&self.username),
            "email" => query.bind(&self.email),
            "role" => query.bind(self.role.to_string()),
            "first_name" => query.bind(&self.first_name),
            "last_name" => query.bind(&self.last_name),
            "home_phone" => query.bind(&self.home_phone),
            "work_phone" => query.bind(&self.work_phone),
            "cell_phone" => query.bind(&self.cell_phone),
            "address" => query.bind(&self.address),
            "address2" => query.bind(&self.address2),
            "city" => query.bind(&self.city),
            "state" => query.bind(&self.state),
            "country" => query.bind(&self.country),
            "notes" => query.bind(&self.notes),
            "created" => query.bind(self.created),
            "updated" => query.bind(self.updated),
            _ => query,
        };
        query
    }

    fn database() -> String {
        "PfAdmin".to_string()
    }

    fn table() -> String {
        "Users".to_string()
    }
}

#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, PartialOrd, Serialize, Deserialize)]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[repr(i64)]
pub enum UserRole {
    #[default]
    None = -1,
    User = 0,
    Viewer = 10,
    Contributor = 20,
    Editor = 30,
    Manager = 40,
    Admin = 50,
    SuperAdmin = i64::MAX,
}
impl Display for UserRole {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            UserRole::None => f.write_str("None"),
            UserRole::User => f.write_str("User"),
            UserRole::Viewer => f.write_str("Viewer"),
            UserRole::Contributor => f.write_str("Contributor"),
            UserRole::Editor => f.write_str("Editor"),
            UserRole::Manager => f.write_str("Manager"),
            UserRole::Admin => f.write_str("Admin"),
            UserRole::SuperAdmin => f.write_str("SuperAdmin"),
        }
    }
}
impl FromStr for UserRole {
    type Err = String;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "user" => Ok(UserRole::User),
            "viewer" => Ok(UserRole::Viewer),
            "contributor" => Ok(UserRole::Contributor),
            "editor" => Ok(UserRole::Editor),
            "manager" => Ok(UserRole::Manager),
            "admin" => Ok(UserRole::Admin),
            "superadmin" => Ok(UserRole::SuperAdmin),
            _ => Err(format!("{s} is not a valid UserRole")),
        }
    }
}
impl From<String> for UserRole {
    fn from(s: String) -> Self {
        Self::from_str(&s).unwrap_or(UserRole::None)
    }
}