feat: Add signature functionality to ContractSigner
- Add `signature_data` field to `ContractSigner` to store base64 encoded signature image data. Allows for storing visual signatures alongside electronic ones. - Implement `sign` method for `ContractSigner` to handle signing with optional signature data and comments. Improves the flexibility and expressiveness of the signing process. - Add Rhai functions for signature management, including signing with/without data and clearing signature data. Extends the Rhai scripting capabilities for contract management. - Add comprehensive unit tests to cover the new signature functionality. Ensures correctness and robustness of the implementation. - Update examples to demonstrate the new signature functionality. Provides clear usage examples for developers.
This commit is contained in:
		@@ -2,6 +2,17 @@ use heromodels_core::BaseModelData;
 | 
			
		||||
use heromodels_derive::model;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use std::fmt;
 | 
			
		||||
use std::time::{SystemTime, UNIX_EPOCH};
 | 
			
		||||
 | 
			
		||||
// --- Helper Functions ---
 | 
			
		||||
 | 
			
		||||
/// Helper function to get current timestamp in seconds
 | 
			
		||||
fn current_timestamp_secs() -> u64 {
 | 
			
		||||
    SystemTime::now()
 | 
			
		||||
        .duration_since(UNIX_EPOCH)
 | 
			
		||||
        .unwrap_or_default()
 | 
			
		||||
        .as_secs()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// --- Enums ---
 | 
			
		||||
 | 
			
		||||
@@ -87,6 +98,7 @@ pub struct ContractSigner {
 | 
			
		||||
    pub signed_at: Option<u64>, // Timestamp
 | 
			
		||||
    pub comments: Option<String>,
 | 
			
		||||
    pub last_reminder_mail_sent_at: Option<u64>, // Unix timestamp of last reminder sent
 | 
			
		||||
    pub signature_data: Option<String>,          // Base64 encoded signature image data
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ContractSigner {
 | 
			
		||||
@@ -99,6 +111,7 @@ impl ContractSigner {
 | 
			
		||||
            signed_at: None,
 | 
			
		||||
            comments: None,
 | 
			
		||||
            last_reminder_mail_sent_at: None,
 | 
			
		||||
            signature_data: None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -137,6 +150,16 @@ impl ContractSigner {
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn signature_data(mut self, signature_data: impl ToString) -> Self {
 | 
			
		||||
        self.signature_data = Some(signature_data.to_string());
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn clear_signature_data(mut self) -> Self {
 | 
			
		||||
        self.signature_data = None;
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Helper method to check if a reminder can be sent (30-minute rate limiting)
 | 
			
		||||
    pub fn can_send_reminder(&self, current_timestamp: u64) -> bool {
 | 
			
		||||
        match self.last_reminder_mail_sent_at {
 | 
			
		||||
@@ -168,6 +191,16 @@ impl ContractSigner {
 | 
			
		||||
    pub fn mark_reminder_sent(&mut self, current_timestamp: u64) {
 | 
			
		||||
        self.last_reminder_mail_sent_at = Some(current_timestamp);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Signs the contract with optional signature data and comments
 | 
			
		||||
    pub fn sign(&mut self, signature_data: Option<String>, comments: Option<String>) {
 | 
			
		||||
        self.status = SignerStatus::Signed;
 | 
			
		||||
        self.signed_at = Some(current_timestamp_secs());
 | 
			
		||||
        self.signature_data = signature_data;
 | 
			
		||||
        if let Some(comment) = comments {
 | 
			
		||||
            self.comments = Some(comment);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// --- Main Contract Model ---
 | 
			
		||||
 
 | 
			
		||||
@@ -211,6 +211,18 @@ pub fn register_legal_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
 | 
			
		||||
        |signer: ContractSigner| -> ContractSigner { signer.clear_last_reminder_mail_sent_at() },
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Signature data functionality
 | 
			
		||||
    engine.register_fn(
 | 
			
		||||
        "signature_data",
 | 
			
		||||
        |signer: ContractSigner, signature_data: String| -> ContractSigner {
 | 
			
		||||
            signer.signature_data(signature_data)
 | 
			
		||||
        },
 | 
			
		||||
    );
 | 
			
		||||
    engine.register_fn(
 | 
			
		||||
        "clear_signature_data",
 | 
			
		||||
        |signer: ContractSigner| -> ContractSigner { signer.clear_signature_data() },
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Helper methods for reminder logic
 | 
			
		||||
    engine.register_fn(
 | 
			
		||||
        "can_send_reminder",
 | 
			
		||||
@@ -263,6 +275,23 @@ pub fn register_legal_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
 | 
			
		||||
        },
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Sign methods
 | 
			
		||||
    engine.register_fn(
 | 
			
		||||
        "sign",
 | 
			
		||||
        |signer: &mut ContractSigner, signature_data: String, comments: String| {
 | 
			
		||||
            signer.sign(Some(signature_data), Some(comments));
 | 
			
		||||
        },
 | 
			
		||||
    );
 | 
			
		||||
    engine.register_fn(
 | 
			
		||||
        "sign_without_signature",
 | 
			
		||||
        |signer: &mut ContractSigner, comments: String| {
 | 
			
		||||
            signer.sign(None, Some(comments));
 | 
			
		||||
        },
 | 
			
		||||
    );
 | 
			
		||||
    engine.register_fn("sign_simple", |signer: &mut ContractSigner| {
 | 
			
		||||
        signer.sign(None, None);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    engine.register_get(
 | 
			
		||||
        "id",
 | 
			
		||||
        |signer: &mut ContractSigner| -> Result<String, Box<EvalAltResult>> {
 | 
			
		||||
@@ -317,6 +346,15 @@ pub fn register_legal_rhai_module(engine: &mut Engine, db: Arc<OurDB>) {
 | 
			
		||||
                .map_or(Dynamic::UNIT, |ts| Dynamic::from(ts as i64)))
 | 
			
		||||
        },
 | 
			
		||||
    );
 | 
			
		||||
    engine.register_get(
 | 
			
		||||
        "signature_data",
 | 
			
		||||
        |signer: &mut ContractSigner| -> Result<Dynamic, Box<EvalAltResult>> {
 | 
			
		||||
            Ok(signer
 | 
			
		||||
                .signature_data
 | 
			
		||||
                .as_ref()
 | 
			
		||||
                .map_or(Dynamic::UNIT, |data| Dynamic::from(data.clone())))
 | 
			
		||||
        },
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // --- Contract ---
 | 
			
		||||
    engine.register_type_with_name::<Contract>("Contract");
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user