green-barrel 1.1.7-beta

ORM-like API MongoDB for Rust.
Documentation

Logo

Green Barrel

ORM-like API MongoDB for Rust

To simulate fields of type ForeignKey and ManyToMany, a simplified alternative (Types of selective fields with dynamic addition of elements) is used.

crates.io crates.io crates.io crates.io

Attention

MongoDB version 4.4

MongoDB Rust Driver version 1.2.5 is used.

Requirements

Model parameters

( all parameters are optional )

Parameter: Default: Description:
db_client_name empty string Used to connect to a MongoDB cluster.
db_query_docs_limit 1000 limiting query results.
is_add_docs true Create documents in the database. false - Alternatively, use it to validate data from web forms.
is_up_docs true Update documents in the database.
is_del_docs true Delete documents from the database.
ignore_fields empty string Fields that are not included in the database (separated by commas).
is_use_add_valid false Allows additional validation - impl AdditionalValidation for ModelName.
is_use_hooks false Allows hooks methods - impl Hooks for ModelName.

Field types

See documentation -fields.

Methods for Developers

Main

  • hash()
  • set_hash()
  • obj_id()
  • set_obj_id()
  • created_at()
  • updated_at()

Caching

  • meta()
  • new()
  • json()
  • update_dyn_field()

Control

  • custom_default()

AdditionalValidation

  • add_validation()

Hooks

  • pre_create()
  • post_create()
  • pre_update()
  • post_update()
  • pre_delete()
  • post_delete()

QCommons

  • aggregate()
  • count_documents()
  • delete_many()
  • delete_one()
  • distinct()
  • drop()
  • estimated_document_count()
  • find_many_to_doc_list()
  • find_many_to_json()
  • find_one_to_doc()
  • find_one_to_json()
  • find_one_to_instance()
  • find_one_and_delete()
  • collection_name()
  • namespace()

QPaladins

  • check()
  • save()
  • delete()
  • create_password_hash()
  • verify_password()
  • update_password()

Fixtures

  • run_fixture()

Install mongodb (if not installed)

### Ubuntu, Mint:
$ sudo apt install mongodb
## OR
### Ubuntu 20.04, Mint 20.x:
$ sudo apt update
$ sudo apt install dirmngr gnupg apt-transport-https ca-certificates
$ wget -qO - https://www.mongodb.org/static/pgp/server-4.4.asc | sudo apt-key add -
$ sudo add-apt-repository 'deb [arch=amd64] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/4.4 multiverse'
$ sudo apt update
$ sudo apt install mongodb-org
$ sudo systemctl enable --now mongod
# For check
$ mongod --version
$ mongo --eval 'db.runCommand({ connectionStatus: 1 })'
$ reboot
#
### Configuration file:
$ sudo nano /etc/mongod.conf
#
### Systemd:
$ sudo systemctl status mongod
$ sudo systemctl start mongod
$ sudo systemctl stop mongod
$ sudo systemctl restart mongod
$ sudo systemctl enable mongod
$ sudo systemctl disable mongod
#
### Uninstall:
$ sudo systemctl stop mongod
$ sudo systemctl disable mongod
$ sudo apt --purge remove mongodb\*  # OR (for mongodb-org) - $ sudo apt --purge remove mongodb-org\**
$ sudo rm -r /var/log/mongodb
$ sudo rm -r /var/lib/mongodb
$ sudo rm -f /etc/mongod.conf
$ sudo apt-add-repository --remove 'deb [arch=amd64] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/4.4 multiverse' # for mongodb-org
$ sudo apt update

Example Usage:

Cargo.toml

[dependencies]
green-barrel = "1.1.7-beta"
metamorphose = "1.1.7-beta"
regex = "1.6.0"
serde_json = "1.0.85"

[dependencies.mongodb]
default-features = false
features = ["sync"]
version = "1.2.5"

[dependencies.serde]
features = ["derive"]
version = "1.0.145"

src/settings.rs

// General settings for the project.
// Project name.
// Hint: PROJECT_NAM it is recommended not to change.
// Valid characters: _ a-z A-Z 0-9
// Max size: 20
// First character: a-z A-Z
pub const PROJECT_NAME: &str = "store";

// Unique project key.
// Hint: UNIQUE_PROJECT_KEY it is recommended not to change.
// Valid characters: a-z A-Z 0-9
// Size: 16
// Example: "7rzgacfqQB3B7q7T"
// To generate a key: https://randompasswordgen.com/
pub const UNIQUE_PROJECT_KEY: &str = "A3iBcq9K19287PN3";

// Settings for user accounts.
pub mod users {
    // Valid characters: _ a-z A-Z 0-9
    // Max size: 30
    // First character: a-z A-Z
    pub const SERVICE_NAME: &str = "accounts";
    // Valid characters: _ a-z A-Z 0-9
    // Max size: 20
    // First character: a-z A-Z
    pub const DATABASE_NAME: &str = "accounts";
    //
    pub const DB_CLIENT_NAME: &str = "default";
    pub const DB_QUERY_DOCS_LIMIT: u32 = 1000;
}

src/migration.rs

use crate::{models, settings};
use green_barrel::{Caching, Monitor, MONGODB_CLIENT_STORE};
use std::error::Error;

// Migration
pub fn run_migration() -> Result<(), Box<dyn Error>> {
    // Caching MongoDB clients.
    {
        let mut client_store = MONGODB_CLIENT_STORE.write()?;
        client_store.insert(
            "default".to_string(),
            mongodb::sync::Client::with_uri_str("mongodb://localhost:27017")?,
        );
    }
    // Monitor initialization.
    let monitor = Monitor {
        project_name: settings::PROJECT_NAME,
        unique_project_key: settings::UNIQUE_PROJECT_KEY,
        // Register models.
        metadata_list: vec![models::User::meta()?],
    };
    // Run migration
    monitor.migrat()?;

    Ok(())
}

src/models.rs

use green_barrel::*;
use metamorphose::Model;
use regex::RegexBuilder;
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, error::Error};

use crate::settings::{
    users::{DATABASE_NAME, DB_CLIENT_NAME, DB_QUERY_DOCS_LIMIT, SERVICE_NAME},
    PROJECT_NAME, UNIQUE_PROJECT_KEY,
};

#[Model(
    is_use_add_valid = true,
    is_use_hooks = true,
    ignore_fields = "confirm_password" // Example: "field_name, field_name_2"
)]
#[derive(Serialize, Deserialize, Default, Debug)]
pub struct User {
    pub username: InputText,
    pub slug: AutoSlug,
    pub first_name: InputText,
    pub last_name: InputText,
    pub email: InputEmail,
    pub phone: InputPhone,
    pub password: InputPassword,
    pub confirm_password: InputPassword,
    pub is_staff: CheckBox,
    pub is_active: CheckBox,
}

impl Control for User {
    fn custom_default() -> Self {
        Self {
            username: InputText {
                label: "Username".into(),
                placeholder: "Enter your username".into(),
                maxlength: 150,
                required: true,
                unique: true,
                hint: "Valid characters: a-z A-Z 0-9 _ @ + .<br>Max size: 150".into(),
                ..Default::default()
            },
            slug: AutoSlug {
                label: "Slug".into(),
                unique: true,
                readonly: true,
                hint: "To create a human readable url".into(),
                slug_sources: vec!["hash".into(), "username".into()],
                ..Default::default()
            },
            first_name: InputText {
                label: "First name".into(),
                placeholder: "Enter your First name".into(),
                maxlength: 150,
                ..Default::default()
            },
            last_name: InputText {
                label: "Last name".into(),
                placeholder: "Enter your Last name".into(),
                maxlength: 150,
                ..Default::default()
            },
            email: InputEmail {
                label: "E-mail".into(),
                placeholder: "Please enter your email".into(),
                required: true,
                unique: true,
                maxlength: 320,
                hint: "Your actual E-mail".into(),
                ..Default::default()
            },
            phone: InputPhone {
                label: "Phone number".into(),
                placeholder: "Please enter your phone number".into(),
                unique: true,
                maxlength: 30,
                hint: "Your actual phone number".into(),
                ..Default::default()
            },
            password: InputPassword {
                label: "Password".into(),
                placeholder: "Enter your password".into(),
                required: true,
                minlength: 8,
                hint: "Valid characters: a-z A-Z 0-9 @ # $ % ^ & + = * ! ~ ) (<br>Min size: 8"
                    .into(),
                ..Default::default()
            },
            confirm_password: InputPassword {
                label: "Confirm password".into(),
                placeholder: "Repeat your password".into(),
                required: true,
                minlength: 8,
                ..Default::default()
            },
            is_staff: CheckBox {
                label: "is staff?".into(),
                checked: Some(true),
                hint: "User can access the admin site?".into(),
                ..Default::default()
            },
            is_active: CheckBox {
                label: "is active?".into(),
                checked: Some(true),
                hint: "Is this an active account?".into(),
                ..Default::default()
            },
            ..Default::default()
        }
    }
}

impl AdditionalValidation for User {
    fn add_validation<'a>(&self) -> Result<HashMap<&'a str, &'a str>, Box<dyn Error>> {
        // Hint: error_map.insert("field_name", "Error message.")
        let mut error_map = HashMap::<&'a str, &'a str>::new();

        // Get clean data
        let hash = self.hash.get().unwrap_or_default();
        let password = self.password.get().unwrap_or_default();
        let confirm_password = self.confirm_password.get().unwrap_or_default();
        let username = self.username.get().unwrap_or_default();

        // Fields validation
        if hash.is_empty() && password != confirm_password {
            error_map.insert("confirm_password", "Password confirmation does not match.");
        }
        if !RegexBuilder::new(r"^[a-z\d_@+.]+$")
            .case_insensitive(true)
            .build()
            .unwrap()
            .is_match(username.as_str())
        {
            error_map.insert(
                "username",
                "Invalid characters present.<br>\
                 Valid characters: a-z A-Z 0-9 _ @ + .",
            );
        }

        Ok(error_map)
    }
}

impl Hooks for User {
    fn pre_create(&self) {
        println!("!!!Pre Create!!!");
    }
    //
    fn post_create(&self) {
        println!("!!!Post Create!!!");
    }
    //
    fn pre_update(&self) {
        println!("!!!Pre Update!!!");
    }
    //
    fn post_update(&self) {
        println!("!!!Post Update!!!");
    }
    //
    fn pre_delete(&self) {
        println!("!!!Pre Delet!!!");
    }
    //
    fn post_delete(&self) {
        println!("!!!Post Delet!!!");
    }
}

src/main.rs

mod migration;
mod models;
mod settings;

use green_barrel::*;
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    // Run migration.
    migration::run_migration()?;

    // Create model instance.
    // ---------------------------------------------------------------------------------------------
    let mut user = models::User::new()?;
    user.username.set("user_1");
    user.email.set("user_1_@noreply.net");
    user.password.set("12345678");
    user.confirm_password.value = Some("12345678".to_string()); // Example without the set() method
    user.is_staff.set(true);
    user.is_active.set(true);

    // Check Model.
    // ---------------------------------------------------------------------------------------------
    println!("\n\nCheck Modell:\n");
    let output_data = user.check(None)?;
    if output_data.is_valid() {
        println!("Hash: {:?}", user.hash.get());
        println!("Hash: {}", output_data.hash());

        println!("Created at: {:?}", user.created_at.get());
        println!("Updated at: {:?}", user.updated_at.get());
        println!("Created at: {:?}", output_data.created_at());
        println!("Updated at: {:?}", output_data.updated_at());

        println!("Object Id: {:?}", user.hash.obj_id()?);
        println!("Object Id: {:?}", output_data.obj_id()?);
    } else {
        // Printing errors to the console ( for development ).
        output_data.print_err();
    }

    //println!("Json:\n{}", output_data.json()?);
    //println!("Json for admin:\n{}", output_data.json_for_admin()?);

    // Create document in database.
    // ---------------------------------------------------------------------------------------------
    println!("\n\nCreate document in database:\n");
    let output_data = user.save(None, None)?;
    if output_data.is_valid() {
        // Update instance.
        user = output_data.update()?;

        println!("Hash: {}", user.hash.get().unwrap());
        println!("Hash: {}", output_data.hash());

        println!("Created at: {}", user.created_at.get().unwrap());
        println!("Updated at: {}", user.updated_at.get().unwrap());
        println!("Created at: {}", output_data.created_at().unwrap());
        println!("Updated at: {}", output_data.updated_at().unwrap());

        println!("Object Id: {:?}", user.hash.obj_id()?.unwrap());
        println!("Object Id: {:?}", output_data.obj_id()?.unwrap());

        //println!("Json:\n{}", output_data.json()?);

        println!("Slug: {}", user.slug.get().unwrap())
    } else {
        // Printing errors to the console ( for development ).
        output_data.print_err();
    }

    // Update document in database.
    // ---------------------------------------------------------------------------------------------
    println!("\n\nUpdate document in database:\n");
    if output_data.is_valid() {
        // Update instance.
        user = output_data.update()?;

        user.username.set("new_user_1");
        let output_data = user.save(None, None)?;

        if output_data.is_valid() {
            // Update instance.
            user = output_data.update()?;

            println!("Hash: {}", user.hash.get().unwrap());
            println!("Hash: {}", output_data.hash());

            println!("Created at: {}", user.created_at.get().unwrap());
            println!("Updated at: {}", user.updated_at.get().unwrap());
            println!("Created at: {}", output_data.created_at().unwrap());
            println!("Updated at: {}", output_data.updated_at().unwrap());

            println!("Object Id: {:?}", user.hash.obj_id()?.unwrap());
            println!("Object Id: {:?}", output_data.obj_id()?.unwrap());

            //println!("Json:\n{}", output_data.json()?);

            println!("Slug: {}", user.slug.get().unwrap())
        } else {
            // Printing errors to the console ( for development ).
            output_data.print_err();
        }
    } else {
        // Printing errors to the console ( for development ).
        output_data.print_err();
    }

    // Delete document in database.
    // ---------------------------------------------------------------------------------------------
    println!("\n\nDelete document in database:\n");
    let output_data = user.delete(None)?;
    if !output_data.is_valid() {
        // Printing errors to the console ( for development ).
        output_data.print_err();
    }

    Ok(())
}

Changelog

  • v1.1.0-beta Added support for Fixtures - To populate the database with pre-created data.
  • v1.0.16-beta Added parameter target_dir for field types InputFile and InputImage.
  • v1.0.10-beta Updated comments for dynamic field types.
  • v1.0.8-beta The 150 character limit has been removed from the update_dyn_field() method.
  • v1.0.7-beta The administrator module has been removed and moved to a separate project.
  • v1.0.0-beta Not compatible with green-barrel v0.x.x and metamorphose v0.x.x
  • v0.12.14 Fixed README.md.
  • v0.12.8 The db_update_dyn_widgets method has been renamed to update_dyn_wig and has been heavily modernized. See documentation: green-barrel > models > caching > Caching > update_dyn_wig.
  • v0.12.4 Made two critical fixes to the check method and updated unit tests.
  • v0.12.0 Deep modernization of the input_data module and related modules.
  • v0.11.4 output_data module moved from widgets directory to models.
  • v0.11.3 administrator module moved from db_query api directory to models.
  • v0.11.2 Renamed methods in trait Administrator - instance_for_admin to actix_instance_for_admin and result_for_admin to actix_result_for_admin.
  • v0.11.1 Added enum OutputDataAdmin for easier registration of Models in the administration panel.
  • v0.11.0 Added trait Administrator for easier registration of Models in the administration panel.
  • v0.10.100 Added new type UpdatePassword to enum OutputData. Updated documentation.
  • v0.10.97 Added field attribute check - pattern.
  • v0.10.95 For optimization, the output_data_to_html mediator function has been excluded.
  • v0.10.94 Added the ability to customize html code for web forms. See documentation: mango_orm > widgets > generate_html > GenerateHtml > generate_html() > source.
  • v0.10.93 Rename trait ToModel to Main.
  • v0.10.92 Added arguments for to_html methods. Arguments: url_action, http_method and enctype. See documentation: mango_orm > widgets > output_data > OutputData > to_html.
  • v0.10.90 For the OutputData enum, the output_data_to_html method is extended with the to_html alias.
  • v0.10.20 Removed the ability to change the created_at field of a model instance.
  • v0.10.0 The created_at and updated_at fields are automatically added to the Model. The widget type is inputDateTime and disabled = true, is_hide = true. Updated README.md. Updated documentation.
  • v0.9.4-v.0.9.15 Import optimized.
  • v0.9.0 Added hook methods. See documentation: mango_orm > models > hooks > Hooks.
  • v0.8.26 Add find_one_to_wig method. See documentation: mango_orm > models > db_query_api > commons > QCommons.
  • v0.8.0 Deep modernization of common.rs and output_data.rs modules. See documentation: mango_orm > models > db_query_api > commons > QCommons and mango_orm > models > output_data > Converters.
  • v0.7.4 Updated README.md, added model attributes.
  • v0.7.0 *Added the ability to use the hash field in inputSlug - slug_sources: r#"["hash", "username"]"#*
  • v0.6.30 Renamed methods: wig(), json(), html() -> to_wig(), to_json(), to_html(). Updated README.md. Updated documentation. Updated versions of dependencies.
  • v0.6.16 Renamed the Forms module to Widgets.
  • v0.6.15 Updating by version of dependencies.
  • v0.6.10 Updated test for dynamic widgets.
  • v0.6.7 Removed hiddenSlug field.
  • v0.6.6 Added is_hide parameter for Widgets.
  • v0.6.5 In the check() method, errors are redirected to the console, for fields of hidden type.
  • v0.6.4 Fixes for fields of slug type.
  • v0.6 1) Added inputSlug and hiddenSlug fields. 2) Fix - Added fields of hidden type to migration.
  • v0.5.4 Optimization for creating thumbnails, for default images.
  • v0.5.3 Improved cleaning of orphaned files.
  • v0.5 Support for the Form macro has been removed.

License

This project is licensed under the MIT and Apache Version 2.0