WIP: development_backend #4
@@ -1,6 +1,6 @@
 | 
			
		||||
use std::env;
 | 
			
		||||
use config::{Config, ConfigError, File};
 | 
			
		||||
use serde::Deserialize;
 | 
			
		||||
use std::env;
 | 
			
		||||
 | 
			
		||||
/// Application configuration
 | 
			
		||||
#[derive(Debug, Deserialize, Clone)]
 | 
			
		||||
@@ -13,6 +13,7 @@ pub struct AppConfig {
 | 
			
		||||
 | 
			
		||||
/// Server configuration
 | 
			
		||||
#[derive(Debug, Deserialize, Clone)]
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
pub struct ServerConfig {
 | 
			
		||||
    /// Host address to bind to
 | 
			
		||||
    pub host: String,
 | 
			
		||||
@@ -50,7 +51,8 @@ impl AppConfig {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Override with environment variables (e.g., SERVER__HOST, SERVER__PORT)
 | 
			
		||||
        config_builder = config_builder.add_source(config::Environment::with_prefix("APP").separator("__"));
 | 
			
		||||
        config_builder =
 | 
			
		||||
            config_builder.add_source(config::Environment::with_prefix("APP").separator("__"));
 | 
			
		||||
 | 
			
		||||
        // Build and deserialize the config
 | 
			
		||||
        let config = config_builder.build()?;
 | 
			
		||||
@@ -61,4 +63,4 @@ impl AppConfig {
 | 
			
		||||
/// Returns the application configuration
 | 
			
		||||
pub fn get_config() -> AppConfig {
 | 
			
		||||
    AppConfig::new().expect("Failed to load configuration")
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -25,6 +25,7 @@ lazy_static! {
 | 
			
		||||
/// Controller for handling authentication-related routes
 | 
			
		||||
pub struct AuthController;
 | 
			
		||||
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
impl AuthController {
 | 
			
		||||
    /// Generate a JWT token for a user
 | 
			
		||||
    fn generate_token(email: &str, role: &UserRole) -> Result<String, jsonwebtoken::errors::Error> {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,12 @@
 | 
			
		||||
use actix_web::{web, HttpResponse, Responder, Result};
 | 
			
		||||
use actix_web::HttpRequest;
 | 
			
		||||
use tera::{Context, Tera};
 | 
			
		||||
use serde::Deserialize;
 | 
			
		||||
use chrono::Utc;
 | 
			
		||||
use crate::utils::render_template;
 | 
			
		||||
use actix_web::HttpRequest;
 | 
			
		||||
use actix_web::{HttpResponse, Result, web};
 | 
			
		||||
use serde::Deserialize;
 | 
			
		||||
use tera::{Context, Tera};
 | 
			
		||||
 | 
			
		||||
// Form structs for company operations
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
pub struct CompanyRegistrationForm {
 | 
			
		||||
    pub company_name: String,
 | 
			
		||||
    pub company_type: String,
 | 
			
		||||
@@ -20,59 +20,69 @@ impl CompanyController {
 | 
			
		||||
    // Display the company management dashboard
 | 
			
		||||
    pub async fn index(tmpl: web::Data<Tera>, req: HttpRequest) -> Result<HttpResponse> {
 | 
			
		||||
        let mut context = Context::new();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        println!("DEBUG: Starting Company dashboard rendering");
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Add active_page for navigation highlighting
 | 
			
		||||
        context.insert("active_page", &"company");
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Parse query parameters
 | 
			
		||||
        let query_string = req.query_string();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Check for success message
 | 
			
		||||
        if let Some(pos) = query_string.find("success=") {
 | 
			
		||||
            let start = pos + 8; // length of "success="
 | 
			
		||||
            let end = query_string[start..].find('&').map_or(query_string.len(), |e| e + start);
 | 
			
		||||
            let end = query_string[start..]
 | 
			
		||||
                .find('&')
 | 
			
		||||
                .map_or(query_string.len(), |e| e + start);
 | 
			
		||||
            let success = &query_string[start..end];
 | 
			
		||||
            let decoded = urlencoding::decode(success).unwrap_or_else(|_| success.into());
 | 
			
		||||
            context.insert("success", &decoded);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Check for entity context
 | 
			
		||||
        if let Some(pos) = query_string.find("entity=") {
 | 
			
		||||
            let start = pos + 7; // length of "entity="
 | 
			
		||||
            let end = query_string[start..].find('&').map_or(query_string.len(), |e| e + start);
 | 
			
		||||
            let end = query_string[start..]
 | 
			
		||||
                .find('&')
 | 
			
		||||
                .map_or(query_string.len(), |e| e + start);
 | 
			
		||||
            let entity = &query_string[start..end];
 | 
			
		||||
            context.insert("entity", &entity);
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            // Also get entity name if present
 | 
			
		||||
            if let Some(pos) = query_string.find("entity_name=") {
 | 
			
		||||
                let start = pos + 12; // length of "entity_name="
 | 
			
		||||
                let end = query_string[start..].find('&').map_or(query_string.len(), |e| e + start);
 | 
			
		||||
                let end = query_string[start..]
 | 
			
		||||
                    .find('&')
 | 
			
		||||
                    .map_or(query_string.len(), |e| e + start);
 | 
			
		||||
                let entity_name = &query_string[start..end];
 | 
			
		||||
                let decoded_name = urlencoding::decode(entity_name).unwrap_or_else(|_| entity_name.into());
 | 
			
		||||
                let decoded_name =
 | 
			
		||||
                    urlencoding::decode(entity_name).unwrap_or_else(|_| entity_name.into());
 | 
			
		||||
                context.insert("entity_name", &decoded_name);
 | 
			
		||||
                println!("DEBUG: Entity context set to {} ({})", entity, decoded_name);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        println!("DEBUG: Rendering Company dashboard template");
 | 
			
		||||
        let response = render_template(&tmpl, "company/index.html", &context);
 | 
			
		||||
        println!("DEBUG: Finished rendering Company dashboard template");
 | 
			
		||||
        response
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // View company details
 | 
			
		||||
    pub async fn view_company(tmpl: web::Data<Tera>, path: web::Path<String>) -> Result<HttpResponse> {
 | 
			
		||||
    pub async fn view_company(
 | 
			
		||||
        tmpl: web::Data<Tera>,
 | 
			
		||||
        path: web::Path<String>,
 | 
			
		||||
    ) -> Result<HttpResponse> {
 | 
			
		||||
        let company_id = path.into_inner();
 | 
			
		||||
        let mut context = Context::new();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        println!("DEBUG: Viewing company details for {}", company_id);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Add active_page for navigation highlighting
 | 
			
		||||
        context.insert("active_page", &"company");
 | 
			
		||||
        context.insert("company_id", &company_id);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // In a real application, we would fetch company data from a database
 | 
			
		||||
        // For now, we'll use mock data based on the company_id
 | 
			
		||||
        match company_id.as_str() {
 | 
			
		||||
@@ -85,14 +95,11 @@ impl CompanyController {
 | 
			
		||||
                context.insert("plan", &"Startup FZC - $50/month");
 | 
			
		||||
                context.insert("next_billing", &"2025-06-01");
 | 
			
		||||
                context.insert("payment_method", &"Credit Card (****4582)");
 | 
			
		||||
                
 | 
			
		||||
 | 
			
		||||
                // Shareholders data
 | 
			
		||||
                let shareholders = vec![
 | 
			
		||||
                    ("John Smith", "60%"),
 | 
			
		||||
                    ("Sarah Johnson", "40%"),
 | 
			
		||||
                ];
 | 
			
		||||
                let shareholders = vec![("John Smith", "60%"), ("Sarah Johnson", "40%")];
 | 
			
		||||
                context.insert("shareholders", &shareholders);
 | 
			
		||||
                
 | 
			
		||||
 | 
			
		||||
                // Contracts data
 | 
			
		||||
                let contracts = vec![
 | 
			
		||||
                    ("Articles of Incorporation", "Signed"),
 | 
			
		||||
@@ -100,7 +107,7 @@ impl CompanyController {
 | 
			
		||||
                    ("Digital Asset Issuance", "Signed"),
 | 
			
		||||
                ];
 | 
			
		||||
                context.insert("contracts", &contracts);
 | 
			
		||||
            },
 | 
			
		||||
            }
 | 
			
		||||
            "company2" => {
 | 
			
		||||
                context.insert("company_name", &"Blockchain Innovations Ltd");
 | 
			
		||||
                context.insert("company_type", &"Growth FZC");
 | 
			
		||||
@@ -110,7 +117,7 @@ impl CompanyController {
 | 
			
		||||
                context.insert("plan", &"Growth FZC - $100/month");
 | 
			
		||||
                context.insert("next_billing", &"2025-06-15");
 | 
			
		||||
                context.insert("payment_method", &"Bank Transfer");
 | 
			
		||||
                
 | 
			
		||||
 | 
			
		||||
                // Shareholders data
 | 
			
		||||
                let shareholders = vec![
 | 
			
		||||
                    ("Michael Chen", "35%"),
 | 
			
		||||
@@ -118,7 +125,7 @@ impl CompanyController {
 | 
			
		||||
                    ("David Okonkwo", "30%"),
 | 
			
		||||
                ];
 | 
			
		||||
                context.insert("shareholders", &shareholders);
 | 
			
		||||
                
 | 
			
		||||
 | 
			
		||||
                // Contracts data
 | 
			
		||||
                let contracts = vec![
 | 
			
		||||
                    ("Articles of Incorporation", "Signed"),
 | 
			
		||||
@@ -127,7 +134,7 @@ impl CompanyController {
 | 
			
		||||
                    ("Physical Asset Holding", "Signed"),
 | 
			
		||||
                ];
 | 
			
		||||
                context.insert("contracts", &contracts);
 | 
			
		||||
            },
 | 
			
		||||
            }
 | 
			
		||||
            "company3" => {
 | 
			
		||||
                context.insert("company_name", &"Sustainable Energy Cooperative");
 | 
			
		||||
                context.insert("company_type", &"Cooperative FZC");
 | 
			
		||||
@@ -137,7 +144,7 @@ impl CompanyController {
 | 
			
		||||
                context.insert("plan", &"Cooperative FZC - $200/month");
 | 
			
		||||
                context.insert("next_billing", &"Pending Activation");
 | 
			
		||||
                context.insert("payment_method", &"Pending");
 | 
			
		||||
                
 | 
			
		||||
 | 
			
		||||
                // Shareholders data
 | 
			
		||||
                let shareholders = vec![
 | 
			
		||||
                    ("Community Energy Group", "40%"),
 | 
			
		||||
@@ -145,7 +152,7 @@ impl CompanyController {
 | 
			
		||||
                    ("Sustainable Living Collective", "30%"),
 | 
			
		||||
                ];
 | 
			
		||||
                context.insert("shareholders", &shareholders);
 | 
			
		||||
                
 | 
			
		||||
 | 
			
		||||
                // Contracts data
 | 
			
		||||
                let contracts = vec![
 | 
			
		||||
                    ("Articles of Incorporation", "Signed"),
 | 
			
		||||
@@ -153,7 +160,7 @@ impl CompanyController {
 | 
			
		||||
                    ("Cooperative Governance", "Pending"),
 | 
			
		||||
                ];
 | 
			
		||||
                context.insert("contracts", &contracts);
 | 
			
		||||
            },
 | 
			
		||||
            }
 | 
			
		||||
            _ => {
 | 
			
		||||
                // If company_id is not recognized, redirect to company index
 | 
			
		||||
                return Ok(HttpResponse::Found()
 | 
			
		||||
@@ -161,51 +168,56 @@ impl CompanyController {
 | 
			
		||||
                    .finish());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        println!("DEBUG: Rendering company view template");
 | 
			
		||||
        let response = render_template(&tmpl, "company/view.html", &context);
 | 
			
		||||
        println!("DEBUG: Finished rendering company view template");
 | 
			
		||||
        response
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Switch to entity context
 | 
			
		||||
    pub async fn switch_entity(path: web::Path<String>) -> Result<HttpResponse> {
 | 
			
		||||
        let company_id = path.into_inner();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        println!("DEBUG: Switching to entity context for {}", company_id);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Get company name based on ID (in a real app, this would come from a database)
 | 
			
		||||
        let company_name = match company_id.as_str() {
 | 
			
		||||
            "company1" => "Zanzibar Digital Solutions",
 | 
			
		||||
            "company2" => "Blockchain Innovations Ltd",
 | 
			
		||||
            "company3" => "Sustainable Energy Cooperative",
 | 
			
		||||
            _ => "Unknown Company"
 | 
			
		||||
            _ => "Unknown Company",
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // In a real application, we would set a session/cookie for the current entity
 | 
			
		||||
        // Here we'll redirect back to the company page with a success message and entity parameter
 | 
			
		||||
        let success_message = format!("Switched to {} entity context", company_name);
 | 
			
		||||
        let encoded_message = urlencoding::encode(&success_message);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        Ok(HttpResponse::Found()
 | 
			
		||||
            .append_header(("Location", format!("/company?success={}&entity={}&entity_name={}", 
 | 
			
		||||
                encoded_message, company_id, urlencoding::encode(company_name))))
 | 
			
		||||
            .append_header((
 | 
			
		||||
                "Location",
 | 
			
		||||
                format!(
 | 
			
		||||
                    "/company?success={}&entity={}&entity_name={}",
 | 
			
		||||
                    encoded_message,
 | 
			
		||||
                    company_id,
 | 
			
		||||
                    urlencoding::encode(company_name)
 | 
			
		||||
                ),
 | 
			
		||||
            ))
 | 
			
		||||
            .finish())
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Process company registration
 | 
			
		||||
    pub async fn register(
 | 
			
		||||
        mut form: actix_multipart::Multipart,
 | 
			
		||||
    ) -> Result<HttpResponse> {
 | 
			
		||||
        use actix_web::{http::header};
 | 
			
		||||
    pub async fn register(mut form: actix_multipart::Multipart) -> Result<HttpResponse> {
 | 
			
		||||
        use actix_web::http::header;
 | 
			
		||||
        use futures_util::stream::StreamExt as _;
 | 
			
		||||
        use std::collections::HashMap;
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        println!("DEBUG: Processing company registration request");
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        let mut fields: HashMap<String, String> = HashMap::new();
 | 
			
		||||
        let mut files = Vec::new();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Parse multipart form
 | 
			
		||||
        while let Some(Ok(mut field)) = form.next().await {
 | 
			
		||||
            let mut value = Vec::new();
 | 
			
		||||
@@ -213,33 +225,47 @@ impl CompanyController {
 | 
			
		||||
                let data = chunk.unwrap();
 | 
			
		||||
                value.extend_from_slice(&data);
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            // Get field name from content disposition
 | 
			
		||||
            let cd = field.content_disposition();
 | 
			
		||||
            if let Some(name) = cd.get_name() {
 | 
			
		||||
                if name == "company_docs" {
 | 
			
		||||
                    files.push(value); // Just collect files in memory for now
 | 
			
		||||
                } else {
 | 
			
		||||
                    fields.insert(name.to_string(), String::from_utf8_lossy(&value).to_string());
 | 
			
		||||
                    fields.insert(
 | 
			
		||||
                        name.to_string(),
 | 
			
		||||
                        String::from_utf8_lossy(&value).to_string(),
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Extract company details
 | 
			
		||||
        let company_name = fields.get("company_name").cloned().unwrap_or_default();
 | 
			
		||||
        let company_type = fields.get("company_type").cloned().unwrap_or_default();
 | 
			
		||||
        let shareholders = fields.get("shareholders").cloned().unwrap_or_default();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Log received fields (mock DB insert)
 | 
			
		||||
        println!("[Company Registration] Name: {}, Type: {}, Shareholders: {}, Files: {}", 
 | 
			
		||||
            company_name, company_type, shareholders, files.len());
 | 
			
		||||
        
 | 
			
		||||
        println!(
 | 
			
		||||
            "[Company Registration] Name: {}, Type: {}, Shareholders: {}, Files: {}",
 | 
			
		||||
            company_name,
 | 
			
		||||
            company_type,
 | 
			
		||||
            shareholders,
 | 
			
		||||
            files.len()
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Create success message
 | 
			
		||||
        let success_message = format!("Successfully registered {} as a {}", company_name, company_type);
 | 
			
		||||
        
 | 
			
		||||
        let success_message = format!(
 | 
			
		||||
            "Successfully registered {} as a {}",
 | 
			
		||||
            company_name, company_type
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Redirect back to /company with success message
 | 
			
		||||
        Ok(HttpResponse::SeeOther()
 | 
			
		||||
            .append_header((header::LOCATION, format!("/company?success={}", urlencoding::encode(&success_message))))
 | 
			
		||||
            .append_header((
 | 
			
		||||
                header::LOCATION,
 | 
			
		||||
                format!("/company?success={}", urlencoding::encode(&success_message)),
 | 
			
		||||
            ))
 | 
			
		||||
            .finish())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,18 @@
 | 
			
		||||
use actix_web::{web, HttpResponse, Result, Error};
 | 
			
		||||
use tera::{Context, Tera};
 | 
			
		||||
use chrono::{Utc, Duration};
 | 
			
		||||
use serde::Deserialize;
 | 
			
		||||
use serde_json::json;
 | 
			
		||||
use actix_web::web::Query;
 | 
			
		||||
use actix_web::{Error, HttpResponse, Result, web};
 | 
			
		||||
use chrono::{Duration, Utc};
 | 
			
		||||
use serde::Deserialize;
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
use tera::{Context, Tera};
 | 
			
		||||
 | 
			
		||||
use crate::models::contract::{Contract, ContractStatus, ContractType, ContractStatistics, ContractSigner, ContractRevision, SignerStatus, TocItem};
 | 
			
		||||
use crate::models::contract::{
 | 
			
		||||
    Contract, ContractRevision, ContractSigner, ContractStatistics, ContractStatus, ContractType,
 | 
			
		||||
    SignerStatus, TocItem,
 | 
			
		||||
};
 | 
			
		||||
use crate::utils::render_template;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
pub struct ContractForm {
 | 
			
		||||
    pub title: String,
 | 
			
		||||
    pub description: String,
 | 
			
		||||
@@ -18,6 +21,7 @@ pub struct ContractForm {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
pub struct SignerForm {
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    pub email: String,
 | 
			
		||||
@@ -29,98 +33,99 @@ impl ContractController {
 | 
			
		||||
    // Display the contracts dashboard
 | 
			
		||||
    pub async fn index(tmpl: web::Data<Tera>) -> Result<HttpResponse, Error> {
 | 
			
		||||
        let mut context = Context::new();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        let contracts = Self::get_mock_contracts();
 | 
			
		||||
        let stats = ContractStatistics::new(&contracts);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Add active_page for navigation highlighting
 | 
			
		||||
        context.insert("active_page", &"contracts");
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Add stats
 | 
			
		||||
        context.insert("stats", &serde_json::to_value(stats).unwrap());
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Add recent contracts
 | 
			
		||||
        let recent_contracts: Vec<serde_json::Map<String, serde_json::Value>> = contracts
 | 
			
		||||
            .iter()
 | 
			
		||||
            .take(5)
 | 
			
		||||
            .map(|c| Self::contract_to_json(c))
 | 
			
		||||
            .collect();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        context.insert("recent_contracts", &recent_contracts);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Add pending signature contracts
 | 
			
		||||
        let pending_signature_contracts: Vec<serde_json::Map<String, serde_json::Value>> = contracts
 | 
			
		||||
            .iter()
 | 
			
		||||
            .filter(|c| c.status == ContractStatus::PendingSignatures)
 | 
			
		||||
            .map(|c| Self::contract_to_json(c))
 | 
			
		||||
            .collect();
 | 
			
		||||
        
 | 
			
		||||
        let pending_signature_contracts: Vec<serde_json::Map<String, serde_json::Value>> =
 | 
			
		||||
            contracts
 | 
			
		||||
                .iter()
 | 
			
		||||
                .filter(|c| c.status == ContractStatus::PendingSignatures)
 | 
			
		||||
                .map(|c| Self::contract_to_json(c))
 | 
			
		||||
                .collect();
 | 
			
		||||
 | 
			
		||||
        context.insert("pending_signature_contracts", &pending_signature_contracts);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Add draft contracts
 | 
			
		||||
        let draft_contracts: Vec<serde_json::Map<String, serde_json::Value>> = contracts
 | 
			
		||||
            .iter()
 | 
			
		||||
            .filter(|c| c.status == ContractStatus::Draft)
 | 
			
		||||
            .map(|c| Self::contract_to_json(c))
 | 
			
		||||
            .collect();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        context.insert("draft_contracts", &draft_contracts);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        render_template(&tmpl, "contracts/index.html", &context)
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Display the list of all contracts
 | 
			
		||||
    pub async fn list(tmpl: web::Data<Tera>) -> Result<HttpResponse, Error> {
 | 
			
		||||
        let mut context = Context::new();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        let contracts = Self::get_mock_contracts();
 | 
			
		||||
        let contracts_data: Vec<serde_json::Map<String, serde_json::Value>> = contracts
 | 
			
		||||
            .iter()
 | 
			
		||||
            .map(|c| Self::contract_to_json(c))
 | 
			
		||||
            .collect();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Add active_page for navigation highlighting
 | 
			
		||||
        context.insert("active_page", &"contracts");
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        context.insert("contracts", &contracts_data);
 | 
			
		||||
        context.insert("filter", &"all");
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        render_template(&tmpl, "contracts/contracts.html", &context)
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Display the list of user's contracts
 | 
			
		||||
    pub async fn my_contracts(tmpl: web::Data<Tera>) -> Result<HttpResponse, Error> {
 | 
			
		||||
        let mut context = Context::new();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        let contracts = Self::get_mock_contracts();
 | 
			
		||||
        let contracts_data: Vec<serde_json::Map<String, serde_json::Value>> = contracts
 | 
			
		||||
            .iter()
 | 
			
		||||
            .map(|c| Self::contract_to_json(c))
 | 
			
		||||
            .collect();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Add active_page for navigation highlighting
 | 
			
		||||
        context.insert("active_page", &"contracts");
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        context.insert("contracts", &contracts_data);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        render_template(&tmpl, "contracts/my_contracts.html", &context)
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Display a specific contract
 | 
			
		||||
    pub async fn detail(
 | 
			
		||||
        tmpl: web::Data<Tera>,
 | 
			
		||||
        path: web::Path<String>,
 | 
			
		||||
        query: Query<HashMap<String, String>>
 | 
			
		||||
        query: Query<HashMap<String, String>>,
 | 
			
		||||
    ) -> Result<HttpResponse, Error> {
 | 
			
		||||
        let contract_id = path.into_inner();
 | 
			
		||||
        let mut context = Context::new();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Add active_page for navigation highlighting
 | 
			
		||||
        context.insert("active_page", &"contracts");
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Find the contract by ID
 | 
			
		||||
        let contracts = Self::get_mock_contracts();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // For demo purposes, if the ID doesn't match exactly, just show the first contract
 | 
			
		||||
        // In a real app, we would return a 404 if the contract is not found
 | 
			
		||||
        let contract = if let Some(found) = contracts.iter().find(|c| c.id == contract_id) {
 | 
			
		||||
@@ -129,7 +134,7 @@ impl ContractController {
 | 
			
		||||
            // For demo, just use the first contract
 | 
			
		||||
            contracts.first().unwrap()
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Convert contract to JSON
 | 
			
		||||
        let contract_json = Self::contract_to_json(contract);
 | 
			
		||||
 | 
			
		||||
@@ -137,10 +142,13 @@ impl ContractController {
 | 
			
		||||
        context.insert("contract", &contract_json);
 | 
			
		||||
 | 
			
		||||
        // If this contract uses multi-page markdown, load the selected section
 | 
			
		||||
        println!("DEBUG: content_dir = {:?}, toc = {:?}", contract.content_dir, contract.toc);
 | 
			
		||||
        println!(
 | 
			
		||||
            "DEBUG: content_dir = {:?}, toc = {:?}",
 | 
			
		||||
            contract.content_dir, contract.toc
 | 
			
		||||
        );
 | 
			
		||||
        if let (Some(content_dir), Some(toc)) = (&contract.content_dir, &contract.toc) {
 | 
			
		||||
            use pulldown_cmark::{Options, Parser, html};
 | 
			
		||||
            use std::fs;
 | 
			
		||||
            use pulldown_cmark::{Parser, Options, html};
 | 
			
		||||
            // Helper to flatten toc recursively
 | 
			
		||||
            fn flatten_toc<'a>(items: &'a Vec<TocItem>, out: &mut Vec<&'a TocItem>) {
 | 
			
		||||
                for item in items {
 | 
			
		||||
@@ -154,15 +162,28 @@ impl ContractController {
 | 
			
		||||
            flatten_toc(&toc, &mut flat_toc);
 | 
			
		||||
            let section_param = query.get("section");
 | 
			
		||||
            let selected_file = section_param
 | 
			
		||||
                .and_then(|f| flat_toc.iter().find(|item| item.file == *f).map(|item| item.file.clone()))
 | 
			
		||||
                .unwrap_or_else(|| flat_toc.get(0).map(|item| item.file.clone()).unwrap_or_default());
 | 
			
		||||
                .and_then(|f| {
 | 
			
		||||
                    flat_toc
 | 
			
		||||
                        .iter()
 | 
			
		||||
                        .find(|item| item.file == *f)
 | 
			
		||||
                        .map(|item| item.file.clone())
 | 
			
		||||
                })
 | 
			
		||||
                .unwrap_or_else(|| {
 | 
			
		||||
                    flat_toc
 | 
			
		||||
                        .get(0)
 | 
			
		||||
                        .map(|item| item.file.clone())
 | 
			
		||||
                        .unwrap_or_default()
 | 
			
		||||
                });
 | 
			
		||||
            context.insert("section", &selected_file);
 | 
			
		||||
            let rel_path = format!("{}/{}", content_dir, selected_file);
 | 
			
		||||
            let abs_path = match std::env::current_dir() {
 | 
			
		||||
                Ok(dir) => dir.join(&rel_path),
 | 
			
		||||
                Err(_) => std::path::PathBuf::from(&rel_path),
 | 
			
		||||
            };
 | 
			
		||||
            println!("DEBUG: Attempting to read markdown file at absolute path: {:?}", abs_path);
 | 
			
		||||
            println!(
 | 
			
		||||
                "DEBUG: Attempting to read markdown file at absolute path: {:?}",
 | 
			
		||||
                abs_path
 | 
			
		||||
            );
 | 
			
		||||
            match fs::read_to_string(&abs_path) {
 | 
			
		||||
                Ok(md) => {
 | 
			
		||||
                    println!("DEBUG: Successfully read markdown file");
 | 
			
		||||
@@ -170,52 +191,63 @@ impl ContractController {
 | 
			
		||||
                    let mut html_output = String::new();
 | 
			
		||||
                    html::push_html(&mut html_output, parser);
 | 
			
		||||
                    context.insert("contract_section_content", &html_output);
 | 
			
		||||
                },
 | 
			
		||||
                }
 | 
			
		||||
                Err(e) => {
 | 
			
		||||
                    let error_msg = format!("Error: Could not read contract section markdown at '{:?}': {}", abs_path, e);
 | 
			
		||||
                    let error_msg = format!(
 | 
			
		||||
                        "Error: Could not read contract section markdown at '{:?}': {}",
 | 
			
		||||
                        abs_path, e
 | 
			
		||||
                    );
 | 
			
		||||
                    println!("{}", error_msg);
 | 
			
		||||
                    context.insert("contract_section_content_error", &error_msg);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            context.insert("toc", &toc);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Count signed signers for the template
 | 
			
		||||
        let signed_signers = contract.signers.iter().filter(|s| s.status == SignerStatus::Signed).count();
 | 
			
		||||
        let signed_signers = contract
 | 
			
		||||
            .signers
 | 
			
		||||
            .iter()
 | 
			
		||||
            .filter(|s| s.status == SignerStatus::Signed)
 | 
			
		||||
            .count();
 | 
			
		||||
        context.insert("signed_signers", &signed_signers);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Count pending signers for the template
 | 
			
		||||
        let pending_signers = contract.signers.iter().filter(|s| s.status == SignerStatus::Pending).count();
 | 
			
		||||
        let pending_signers = contract
 | 
			
		||||
            .signers
 | 
			
		||||
            .iter()
 | 
			
		||||
            .filter(|s| s.status == SignerStatus::Pending)
 | 
			
		||||
            .count();
 | 
			
		||||
        context.insert("pending_signers", &pending_signers);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // For demo purposes, set user_has_signed to false
 | 
			
		||||
        // In a real app, we would check if the current user has already signed
 | 
			
		||||
        context.insert("user_has_signed", &false);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        render_template(&tmpl, "contracts/contract_detail.html", &context)
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Display the create contract form
 | 
			
		||||
    pub async fn create_form(tmpl: web::Data<Tera>) -> Result<HttpResponse, Error> {
 | 
			
		||||
        let mut context = Context::new();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Add active_page for navigation highlighting
 | 
			
		||||
        context.insert("active_page", &"contracts");
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Add contract types for dropdown
 | 
			
		||||
        let contract_types = vec![
 | 
			
		||||
            ("Service", "Service Agreement"),
 | 
			
		||||
            ("Employment", "Employment Contract"),
 | 
			
		||||
            ("NDA", "Non-Disclosure Agreement"),
 | 
			
		||||
            ("SLA", "Service Level Agreement"),
 | 
			
		||||
            ("Other", "Other")
 | 
			
		||||
            ("Other", "Other"),
 | 
			
		||||
        ];
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        context.insert("contract_types", &contract_types);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        render_template(&tmpl, "contracts/create_contract.html", &context)
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Process the create contract form
 | 
			
		||||
    pub async fn create(
 | 
			
		||||
        _tmpl: web::Data<Tera>,
 | 
			
		||||
@@ -223,158 +255,334 @@ impl ContractController {
 | 
			
		||||
    ) -> Result<HttpResponse, Error> {
 | 
			
		||||
        // In a real application, we would save the contract to the database
 | 
			
		||||
        // For now, we'll just redirect to the contracts list
 | 
			
		||||
        
 | 
			
		||||
        Ok(HttpResponse::Found().append_header(("Location", "/contracts")).finish())
 | 
			
		||||
 | 
			
		||||
        Ok(HttpResponse::Found()
 | 
			
		||||
            .append_header(("Location", "/contracts"))
 | 
			
		||||
            .finish())
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Helper method to convert Contract to a JSON object for templates
 | 
			
		||||
    fn contract_to_json(contract: &Contract) -> serde_json::Map<String, serde_json::Value> {
 | 
			
		||||
        let mut map = serde_json::Map::new();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Basic contract info
 | 
			
		||||
        map.insert("id".to_string(), serde_json::Value::String(contract.id.clone()));
 | 
			
		||||
        map.insert("title".to_string(), serde_json::Value::String(contract.title.clone()));
 | 
			
		||||
        map.insert("description".to_string(), serde_json::Value::String(contract.description.clone()));
 | 
			
		||||
        map.insert("status".to_string(), serde_json::Value::String(contract.status.as_str().to_string()));
 | 
			
		||||
        map.insert("contract_type".to_string(), serde_json::Value::String(contract.contract_type.as_str().to_string()));
 | 
			
		||||
        map.insert("created_by".to_string(), serde_json::Value::String(contract.created_by.clone()));
 | 
			
		||||
        map.insert("created_at".to_string(), serde_json::Value::String(contract.created_at.format("%Y-%m-%d").to_string()));
 | 
			
		||||
        map.insert("updated_at".to_string(), serde_json::Value::String(contract.updated_at.format("%Y-%m-%d").to_string()));
 | 
			
		||||
        
 | 
			
		||||
        map.insert(
 | 
			
		||||
            "id".to_string(),
 | 
			
		||||
            serde_json::Value::String(contract.id.clone()),
 | 
			
		||||
        );
 | 
			
		||||
        map.insert(
 | 
			
		||||
            "title".to_string(),
 | 
			
		||||
            serde_json::Value::String(contract.title.clone()),
 | 
			
		||||
        );
 | 
			
		||||
        map.insert(
 | 
			
		||||
            "description".to_string(),
 | 
			
		||||
            serde_json::Value::String(contract.description.clone()),
 | 
			
		||||
        );
 | 
			
		||||
        map.insert(
 | 
			
		||||
            "status".to_string(),
 | 
			
		||||
            serde_json::Value::String(contract.status.as_str().to_string()),
 | 
			
		||||
        );
 | 
			
		||||
        map.insert(
 | 
			
		||||
            "contract_type".to_string(),
 | 
			
		||||
            serde_json::Value::String(contract.contract_type.as_str().to_string()),
 | 
			
		||||
        );
 | 
			
		||||
        map.insert(
 | 
			
		||||
            "created_by".to_string(),
 | 
			
		||||
            serde_json::Value::String(contract.created_by.clone()),
 | 
			
		||||
        );
 | 
			
		||||
        map.insert(
 | 
			
		||||
            "created_at".to_string(),
 | 
			
		||||
            serde_json::Value::String(contract.created_at.format("%Y-%m-%d").to_string()),
 | 
			
		||||
        );
 | 
			
		||||
        map.insert(
 | 
			
		||||
            "updated_at".to_string(),
 | 
			
		||||
            serde_json::Value::String(contract.updated_at.format("%Y-%m-%d").to_string()),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Organization info
 | 
			
		||||
        if let Some(org) = &contract.organization_id {
 | 
			
		||||
            map.insert("organization".to_string(), serde_json::Value::String(org.clone()));
 | 
			
		||||
            map.insert(
 | 
			
		||||
                "organization".to_string(),
 | 
			
		||||
                serde_json::Value::String(org.clone()),
 | 
			
		||||
            );
 | 
			
		||||
        } else {
 | 
			
		||||
            map.insert("organization".to_string(), serde_json::Value::Null);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Add signers
 | 
			
		||||
        let signers: Vec<serde_json::Value> = contract.signers.iter()
 | 
			
		||||
        let signers: Vec<serde_json::Value> = contract
 | 
			
		||||
            .signers
 | 
			
		||||
            .iter()
 | 
			
		||||
            .map(|s| {
 | 
			
		||||
                let mut signer_map = serde_json::Map::new();
 | 
			
		||||
                signer_map.insert("id".to_string(), serde_json::Value::String(s.id.clone()));
 | 
			
		||||
                signer_map.insert("name".to_string(), serde_json::Value::String(s.name.clone()));
 | 
			
		||||
                signer_map.insert("email".to_string(), serde_json::Value::String(s.email.clone()));
 | 
			
		||||
                signer_map.insert("status".to_string(), serde_json::Value::String(s.status.as_str().to_string()));
 | 
			
		||||
                
 | 
			
		||||
                signer_map.insert(
 | 
			
		||||
                    "name".to_string(),
 | 
			
		||||
                    serde_json::Value::String(s.name.clone()),
 | 
			
		||||
                );
 | 
			
		||||
                signer_map.insert(
 | 
			
		||||
                    "email".to_string(),
 | 
			
		||||
                    serde_json::Value::String(s.email.clone()),
 | 
			
		||||
                );
 | 
			
		||||
                signer_map.insert(
 | 
			
		||||
                    "status".to_string(),
 | 
			
		||||
                    serde_json::Value::String(s.status.as_str().to_string()),
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                if let Some(signed_at) = s.signed_at {
 | 
			
		||||
                    signer_map.insert("signed_at".to_string(), serde_json::Value::String(signed_at.format("%Y-%m-%d").to_string()));
 | 
			
		||||
                    signer_map.insert(
 | 
			
		||||
                        "signed_at".to_string(),
 | 
			
		||||
                        serde_json::Value::String(signed_at.format("%Y-%m-%d").to_string()),
 | 
			
		||||
                    );
 | 
			
		||||
                } else {
 | 
			
		||||
                    // For display purposes, add a placeholder date for pending signers
 | 
			
		||||
                    if s.status == SignerStatus::Pending {
 | 
			
		||||
                        signer_map.insert("signed_at".to_string(), serde_json::Value::String("Pending".to_string()));
 | 
			
		||||
                        signer_map.insert(
 | 
			
		||||
                            "signed_at".to_string(),
 | 
			
		||||
                            serde_json::Value::String("Pending".to_string()),
 | 
			
		||||
                        );
 | 
			
		||||
                    } else if s.status == SignerStatus::Rejected {
 | 
			
		||||
                        signer_map.insert("signed_at".to_string(), serde_json::Value::String("Rejected".to_string()));
 | 
			
		||||
                        signer_map.insert(
 | 
			
		||||
                            "signed_at".to_string(),
 | 
			
		||||
                            serde_json::Value::String("Rejected".to_string()),
 | 
			
		||||
                        );
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
 | 
			
		||||
                if let Some(comments) = &s.comments {
 | 
			
		||||
                    signer_map.insert("comments".to_string(), serde_json::Value::String(comments.clone()));
 | 
			
		||||
                    signer_map.insert(
 | 
			
		||||
                        "comments".to_string(),
 | 
			
		||||
                        serde_json::Value::String(comments.clone()),
 | 
			
		||||
                    );
 | 
			
		||||
                } else {
 | 
			
		||||
                    signer_map.insert("comments".to_string(), serde_json::Value::String("".to_string()));
 | 
			
		||||
                    signer_map.insert(
 | 
			
		||||
                        "comments".to_string(),
 | 
			
		||||
                        serde_json::Value::String("".to_string()),
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
 | 
			
		||||
                serde_json::Value::Object(signer_map)
 | 
			
		||||
            })
 | 
			
		||||
            .collect();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        map.insert("signers".to_string(), serde_json::Value::Array(signers));
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Add pending_signers count for templates
 | 
			
		||||
        let pending_signers = contract.signers.iter().filter(|s| s.status == SignerStatus::Pending).count();
 | 
			
		||||
        map.insert("pending_signers".to_string(), serde_json::Value::Number(serde_json::Number::from(pending_signers)));
 | 
			
		||||
        
 | 
			
		||||
        let pending_signers = contract
 | 
			
		||||
            .signers
 | 
			
		||||
            .iter()
 | 
			
		||||
            .filter(|s| s.status == SignerStatus::Pending)
 | 
			
		||||
            .count();
 | 
			
		||||
        map.insert(
 | 
			
		||||
            "pending_signers".to_string(),
 | 
			
		||||
            serde_json::Value::Number(serde_json::Number::from(pending_signers)),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Add signed_signers count for templates
 | 
			
		||||
        let signed_signers = contract.signers.iter().filter(|s| s.status == SignerStatus::Signed).count();
 | 
			
		||||
        map.insert("signed_signers".to_string(), serde_json::Value::Number(serde_json::Number::from(signed_signers)));
 | 
			
		||||
        
 | 
			
		||||
        let signed_signers = contract
 | 
			
		||||
            .signers
 | 
			
		||||
            .iter()
 | 
			
		||||
            .filter(|s| s.status == SignerStatus::Signed)
 | 
			
		||||
            .count();
 | 
			
		||||
        map.insert(
 | 
			
		||||
            "signed_signers".to_string(),
 | 
			
		||||
            serde_json::Value::Number(serde_json::Number::from(signed_signers)),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Add revisions
 | 
			
		||||
        let revisions: Vec<serde_json::Value> = contract.revisions.iter()
 | 
			
		||||
        let revisions: Vec<serde_json::Value> = contract
 | 
			
		||||
            .revisions
 | 
			
		||||
            .iter()
 | 
			
		||||
            .map(|r| {
 | 
			
		||||
                let mut revision_map = serde_json::Map::new();
 | 
			
		||||
                revision_map.insert("version".to_string(), serde_json::Value::Number(serde_json::Number::from(r.version)));
 | 
			
		||||
                revision_map.insert("content".to_string(), serde_json::Value::String(r.content.clone()));
 | 
			
		||||
                revision_map.insert("created_at".to_string(), serde_json::Value::String(r.created_at.format("%Y-%m-%d").to_string()));
 | 
			
		||||
                revision_map.insert("created_by".to_string(), serde_json::Value::String(r.created_by.clone()));
 | 
			
		||||
                
 | 
			
		||||
                revision_map.insert(
 | 
			
		||||
                    "version".to_string(),
 | 
			
		||||
                    serde_json::Value::Number(serde_json::Number::from(r.version)),
 | 
			
		||||
                );
 | 
			
		||||
                revision_map.insert(
 | 
			
		||||
                    "content".to_string(),
 | 
			
		||||
                    serde_json::Value::String(r.content.clone()),
 | 
			
		||||
                );
 | 
			
		||||
                revision_map.insert(
 | 
			
		||||
                    "created_at".to_string(),
 | 
			
		||||
                    serde_json::Value::String(r.created_at.format("%Y-%m-%d").to_string()),
 | 
			
		||||
                );
 | 
			
		||||
                revision_map.insert(
 | 
			
		||||
                    "created_by".to_string(),
 | 
			
		||||
                    serde_json::Value::String(r.created_by.clone()),
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                if let Some(comments) = &r.comments {
 | 
			
		||||
                    revision_map.insert("comments".to_string(), serde_json::Value::String(comments.clone()));
 | 
			
		||||
                    revision_map.insert(
 | 
			
		||||
                        "comments".to_string(),
 | 
			
		||||
                        serde_json::Value::String(comments.clone()),
 | 
			
		||||
                    );
 | 
			
		||||
                    // Add notes field using comments since ContractRevision doesn't have a notes field
 | 
			
		||||
                    revision_map.insert("notes".to_string(), serde_json::Value::String(comments.clone()));
 | 
			
		||||
                    revision_map.insert(
 | 
			
		||||
                        "notes".to_string(),
 | 
			
		||||
                        serde_json::Value::String(comments.clone()),
 | 
			
		||||
                    );
 | 
			
		||||
                } else {
 | 
			
		||||
                    revision_map.insert("comments".to_string(), serde_json::Value::String("".to_string()));
 | 
			
		||||
                    revision_map.insert("notes".to_string(), serde_json::Value::String("".to_string()));
 | 
			
		||||
                    revision_map.insert(
 | 
			
		||||
                        "comments".to_string(),
 | 
			
		||||
                        serde_json::Value::String("".to_string()),
 | 
			
		||||
                    );
 | 
			
		||||
                    revision_map.insert(
 | 
			
		||||
                        "notes".to_string(),
 | 
			
		||||
                        serde_json::Value::String("".to_string()),
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
 | 
			
		||||
                serde_json::Value::Object(revision_map)
 | 
			
		||||
            })
 | 
			
		||||
            .collect();
 | 
			
		||||
        
 | 
			
		||||
        map.insert("revisions".to_string(), serde_json::Value::Array(revisions.clone()));
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        map.insert(
 | 
			
		||||
            "revisions".to_string(),
 | 
			
		||||
            serde_json::Value::Array(revisions.clone()),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Add current_version
 | 
			
		||||
        map.insert("current_version".to_string(), serde_json::Value::Number(serde_json::Number::from(contract.current_version)));
 | 
			
		||||
        
 | 
			
		||||
        map.insert(
 | 
			
		||||
            "current_version".to_string(),
 | 
			
		||||
            serde_json::Value::Number(serde_json::Number::from(contract.current_version)),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Add latest_revision as an object
 | 
			
		||||
        if !contract.revisions.is_empty() {
 | 
			
		||||
            // Find the latest revision based on version number
 | 
			
		||||
            if let Some(latest) = contract.revisions.iter().max_by_key(|r| r.version) {
 | 
			
		||||
                let mut latest_revision_map = serde_json::Map::new();
 | 
			
		||||
                latest_revision_map.insert("version".to_string(), serde_json::Value::Number(serde_json::Number::from(latest.version)));
 | 
			
		||||
                latest_revision_map.insert("content".to_string(), serde_json::Value::String(latest.content.clone()));
 | 
			
		||||
                latest_revision_map.insert("created_at".to_string(), serde_json::Value::String(latest.created_at.format("%Y-%m-%d").to_string()));
 | 
			
		||||
                latest_revision_map.insert("created_by".to_string(), serde_json::Value::String(latest.created_by.clone()));
 | 
			
		||||
                
 | 
			
		||||
                latest_revision_map.insert(
 | 
			
		||||
                    "version".to_string(),
 | 
			
		||||
                    serde_json::Value::Number(serde_json::Number::from(latest.version)),
 | 
			
		||||
                );
 | 
			
		||||
                latest_revision_map.insert(
 | 
			
		||||
                    "content".to_string(),
 | 
			
		||||
                    serde_json::Value::String(latest.content.clone()),
 | 
			
		||||
                );
 | 
			
		||||
                latest_revision_map.insert(
 | 
			
		||||
                    "created_at".to_string(),
 | 
			
		||||
                    serde_json::Value::String(latest.created_at.format("%Y-%m-%d").to_string()),
 | 
			
		||||
                );
 | 
			
		||||
                latest_revision_map.insert(
 | 
			
		||||
                    "created_by".to_string(),
 | 
			
		||||
                    serde_json::Value::String(latest.created_by.clone()),
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                if let Some(comments) = &latest.comments {
 | 
			
		||||
                    latest_revision_map.insert("comments".to_string(), serde_json::Value::String(comments.clone()));
 | 
			
		||||
                    latest_revision_map.insert("notes".to_string(), serde_json::Value::String(comments.clone()));
 | 
			
		||||
                    latest_revision_map.insert(
 | 
			
		||||
                        "comments".to_string(),
 | 
			
		||||
                        serde_json::Value::String(comments.clone()),
 | 
			
		||||
                    );
 | 
			
		||||
                    latest_revision_map.insert(
 | 
			
		||||
                        "notes".to_string(),
 | 
			
		||||
                        serde_json::Value::String(comments.clone()),
 | 
			
		||||
                    );
 | 
			
		||||
                } else {
 | 
			
		||||
                    latest_revision_map.insert("comments".to_string(), serde_json::Value::String("".to_string()));
 | 
			
		||||
                    latest_revision_map.insert("notes".to_string(), serde_json::Value::String("".to_string()));
 | 
			
		||||
                    latest_revision_map.insert(
 | 
			
		||||
                        "comments".to_string(),
 | 
			
		||||
                        serde_json::Value::String("".to_string()),
 | 
			
		||||
                    );
 | 
			
		||||
                    latest_revision_map.insert(
 | 
			
		||||
                        "notes".to_string(),
 | 
			
		||||
                        serde_json::Value::String("".to_string()),
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                map.insert("latest_revision".to_string(), serde_json::Value::Object(latest_revision_map));
 | 
			
		||||
 | 
			
		||||
                map.insert(
 | 
			
		||||
                    "latest_revision".to_string(),
 | 
			
		||||
                    serde_json::Value::Object(latest_revision_map),
 | 
			
		||||
                );
 | 
			
		||||
            } else {
 | 
			
		||||
                // Create an empty latest_revision object to avoid template errors
 | 
			
		||||
                let mut empty_revision = serde_json::Map::new();
 | 
			
		||||
                empty_revision.insert("version".to_string(), serde_json::Value::Number(serde_json::Number::from(0)));
 | 
			
		||||
                empty_revision.insert("content".to_string(), serde_json::Value::String("No content available".to_string()));
 | 
			
		||||
                empty_revision.insert("created_at".to_string(), serde_json::Value::String("N/A".to_string()));
 | 
			
		||||
                empty_revision.insert("created_by".to_string(), serde_json::Value::String("N/A".to_string()));
 | 
			
		||||
                empty_revision.insert("comments".to_string(), serde_json::Value::String("".to_string()));
 | 
			
		||||
                empty_revision.insert("notes".to_string(), serde_json::Value::String("".to_string()));
 | 
			
		||||
                
 | 
			
		||||
                map.insert("latest_revision".to_string(), serde_json::Value::Object(empty_revision));
 | 
			
		||||
                empty_revision.insert(
 | 
			
		||||
                    "version".to_string(),
 | 
			
		||||
                    serde_json::Value::Number(serde_json::Number::from(0)),
 | 
			
		||||
                );
 | 
			
		||||
                empty_revision.insert(
 | 
			
		||||
                    "content".to_string(),
 | 
			
		||||
                    serde_json::Value::String("No content available".to_string()),
 | 
			
		||||
                );
 | 
			
		||||
                empty_revision.insert(
 | 
			
		||||
                    "created_at".to_string(),
 | 
			
		||||
                    serde_json::Value::String("N/A".to_string()),
 | 
			
		||||
                );
 | 
			
		||||
                empty_revision.insert(
 | 
			
		||||
                    "created_by".to_string(),
 | 
			
		||||
                    serde_json::Value::String("N/A".to_string()),
 | 
			
		||||
                );
 | 
			
		||||
                empty_revision.insert(
 | 
			
		||||
                    "comments".to_string(),
 | 
			
		||||
                    serde_json::Value::String("".to_string()),
 | 
			
		||||
                );
 | 
			
		||||
                empty_revision.insert(
 | 
			
		||||
                    "notes".to_string(),
 | 
			
		||||
                    serde_json::Value::String("".to_string()),
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                map.insert(
 | 
			
		||||
                    "latest_revision".to_string(),
 | 
			
		||||
                    serde_json::Value::Object(empty_revision),
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            // Create an empty latest_revision object to avoid template errors
 | 
			
		||||
            let mut empty_revision = serde_json::Map::new();
 | 
			
		||||
            empty_revision.insert("version".to_string(), serde_json::Value::Number(serde_json::Number::from(0)));
 | 
			
		||||
            empty_revision.insert("content".to_string(), serde_json::Value::String("No content available".to_string()));
 | 
			
		||||
            empty_revision.insert("created_at".to_string(), serde_json::Value::String("N/A".to_string()));
 | 
			
		||||
            empty_revision.insert("created_by".to_string(), serde_json::Value::String("N/A".to_string()));
 | 
			
		||||
            empty_revision.insert("comments".to_string(), serde_json::Value::String("".to_string()));
 | 
			
		||||
            empty_revision.insert("notes".to_string(), serde_json::Value::String("".to_string()));
 | 
			
		||||
            
 | 
			
		||||
            map.insert("latest_revision".to_string(), serde_json::Value::Object(empty_revision));
 | 
			
		||||
            empty_revision.insert(
 | 
			
		||||
                "version".to_string(),
 | 
			
		||||
                serde_json::Value::Number(serde_json::Number::from(0)),
 | 
			
		||||
            );
 | 
			
		||||
            empty_revision.insert(
 | 
			
		||||
                "content".to_string(),
 | 
			
		||||
                serde_json::Value::String("No content available".to_string()),
 | 
			
		||||
            );
 | 
			
		||||
            empty_revision.insert(
 | 
			
		||||
                "created_at".to_string(),
 | 
			
		||||
                serde_json::Value::String("N/A".to_string()),
 | 
			
		||||
            );
 | 
			
		||||
            empty_revision.insert(
 | 
			
		||||
                "created_by".to_string(),
 | 
			
		||||
                serde_json::Value::String("N/A".to_string()),
 | 
			
		||||
            );
 | 
			
		||||
            empty_revision.insert(
 | 
			
		||||
                "comments".to_string(),
 | 
			
		||||
                serde_json::Value::String("".to_string()),
 | 
			
		||||
            );
 | 
			
		||||
            empty_revision.insert(
 | 
			
		||||
                "notes".to_string(),
 | 
			
		||||
                serde_json::Value::String("".to_string()),
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            map.insert(
 | 
			
		||||
                "latest_revision".to_string(),
 | 
			
		||||
                serde_json::Value::Object(empty_revision),
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Add effective and expiration dates if present
 | 
			
		||||
        if let Some(effective_date) = &contract.effective_date {
 | 
			
		||||
            map.insert("effective_date".to_string(), serde_json::Value::String(effective_date.format("%Y-%m-%d").to_string()));
 | 
			
		||||
            map.insert(
 | 
			
		||||
                "effective_date".to_string(),
 | 
			
		||||
                serde_json::Value::String(effective_date.format("%Y-%m-%d").to_string()),
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        if let Some(expiration_date) = &contract.expiration_date {
 | 
			
		||||
            map.insert("expiration_date".to_string(), serde_json::Value::String(expiration_date.format("%Y-%m-%d").to_string()));
 | 
			
		||||
            map.insert(
 | 
			
		||||
                "expiration_date".to_string(),
 | 
			
		||||
                serde_json::Value::String(expiration_date.format("%Y-%m-%d").to_string()),
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        map
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Generate mock contracts for testing
 | 
			
		||||
    fn get_mock_contracts() -> Vec<Contract> {
 | 
			
		||||
        let mut contracts = Vec::new();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Mock contract 1 - Signed Service Agreement
 | 
			
		||||
        let mut contract1 = Contract {
 | 
			
		||||
            content_dir: None,
 | 
			
		||||
@@ -394,7 +602,7 @@ impl ContractController {
 | 
			
		||||
            revisions: Vec::new(),
 | 
			
		||||
            current_version: 2,
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Add signers to contract 1
 | 
			
		||||
        contract1.signers.push(ContractSigner {
 | 
			
		||||
            id: "signer-001".to_string(),
 | 
			
		||||
@@ -404,7 +612,7 @@ impl ContractController {
 | 
			
		||||
            signed_at: Some(Utc::now() - Duration::days(5)),
 | 
			
		||||
            comments: Some("Approved as per our discussion.".to_string()),
 | 
			
		||||
        });
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        contract1.signers.push(ContractSigner {
 | 
			
		||||
            id: "signer-002".to_string(),
 | 
			
		||||
            name: "Nala Okafor".to_string(),
 | 
			
		||||
@@ -413,7 +621,7 @@ impl ContractController {
 | 
			
		||||
            signed_at: Some(Utc::now() - Duration::days(6)),
 | 
			
		||||
            comments: Some("Terms look good. Happy to proceed.".to_string()),
 | 
			
		||||
        });
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Add revisions to contract 1
 | 
			
		||||
        contract1.revisions.push(ContractRevision {
 | 
			
		||||
            version: 1,
 | 
			
		||||
@@ -422,7 +630,7 @@ impl ContractController {
 | 
			
		||||
            created_by: "Wei Chen".to_string(),
 | 
			
		||||
            comments: Some("Initial draft of the service agreement.".to_string()),
 | 
			
		||||
        });
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        contract1.revisions.push(ContractRevision {
 | 
			
		||||
            version: 2,
 | 
			
		||||
            content: "<h1>Digital Hub Service Agreement</h1><p>This Service Agreement (the \"Agreement\") is entered into between Zanzibar Digital Hub (\"Provider\") and the undersigned client (\"Client\").</p><h2>1. Services</h2><p>Provider agrees to provide Client with cloud hosting and digital infrastructure services as specified in Appendix A.</p><h2>2. Term</h2><p>This Agreement shall commence on the Effective Date and continue for a period of one (1) year unless terminated earlier in accordance with the terms herein.</p><h2>3. Fees</h2><p>Client agrees to pay Provider the fees set forth in Appendix B. All fees are due within thirty (30) days of invoice date.</p><h2>4. Confidentiality</h2><p>Each party agrees to maintain the confidentiality of any proprietary information received from the other party during the term of this Agreement.</p><h2>5. Data Protection</h2><p>Provider shall implement appropriate technical and organizational measures to ensure a level of security appropriate to the risk, including encryption of personal data, and shall comply with all applicable data protection laws.</p>".to_string(),
 | 
			
		||||
@@ -430,7 +638,7 @@ impl ContractController {
 | 
			
		||||
            created_by: "Wei Chen".to_string(),
 | 
			
		||||
            comments: Some("Added data protection clause as requested by legal.".to_string()),
 | 
			
		||||
        });
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Mock contract 2 - Pending Signatures
 | 
			
		||||
        let mut contract2 = Contract {
 | 
			
		||||
            content_dir: None,
 | 
			
		||||
@@ -450,7 +658,7 @@ impl ContractController {
 | 
			
		||||
            revisions: Vec::new(),
 | 
			
		||||
            current_version: 1,
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Add signers to contract 2
 | 
			
		||||
        contract2.signers.push(ContractSigner {
 | 
			
		||||
            id: "signer-003".to_string(),
 | 
			
		||||
@@ -460,7 +668,7 @@ impl ContractController {
 | 
			
		||||
            signed_at: Some(Utc::now() - Duration::days(2)),
 | 
			
		||||
            comments: None,
 | 
			
		||||
        });
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        contract2.signers.push(ContractSigner {
 | 
			
		||||
            id: "signer-004".to_string(),
 | 
			
		||||
            name: "Maya Rodriguez".to_string(),
 | 
			
		||||
@@ -469,7 +677,7 @@ impl ContractController {
 | 
			
		||||
            signed_at: None,
 | 
			
		||||
            comments: None,
 | 
			
		||||
        });
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        contract2.signers.push(ContractSigner {
 | 
			
		||||
            id: "signer-005".to_string(),
 | 
			
		||||
            name: "Jamal Washington".to_string(),
 | 
			
		||||
@@ -478,7 +686,7 @@ impl ContractController {
 | 
			
		||||
            signed_at: None,
 | 
			
		||||
            comments: None,
 | 
			
		||||
        });
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Add revisions to contract 2
 | 
			
		||||
        contract2.revisions.push(ContractRevision {
 | 
			
		||||
            version: 1,
 | 
			
		||||
@@ -487,7 +695,7 @@ impl ContractController {
 | 
			
		||||
            created_by: "Dr. Raj Patel".to_string(),
 | 
			
		||||
            comments: Some("Initial draft of the development agreement.".to_string()),
 | 
			
		||||
        });
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Mock contract 3 - Draft
 | 
			
		||||
        let mut contract3 = Contract {
 | 
			
		||||
            id: "contract-003".to_string(),
 | 
			
		||||
@@ -554,7 +762,6 @@ impl ContractController {
 | 
			
		||||
            ]),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
        // Add potential signers to contract 3 (still in draft)
 | 
			
		||||
        contract3.signers.push(ContractSigner {
 | 
			
		||||
            id: "signer-006".to_string(),
 | 
			
		||||
@@ -564,7 +771,7 @@ impl ContractController {
 | 
			
		||||
            signed_at: None,
 | 
			
		||||
            comments: None,
 | 
			
		||||
        });
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        contract3.signers.push(ContractSigner {
 | 
			
		||||
            id: "signer-007".to_string(),
 | 
			
		||||
            name: "Ibrahim Al-Farsi".to_string(),
 | 
			
		||||
@@ -573,59 +780,57 @@ impl ContractController {
 | 
			
		||||
            signed_at: None,
 | 
			
		||||
            comments: None,
 | 
			
		||||
        });
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Add ToC and content directory to contract 3
 | 
			
		||||
        contract3.content_dir = Some("src/content/contract-003".to_string());
 | 
			
		||||
        contract3.toc = Some(vec![
 | 
			
		||||
            TocItem {
 | 
			
		||||
                title: "Digital Asset Tokenization Agreement".to_string(),
 | 
			
		||||
                file: "cover.md".to_string(),
 | 
			
		||||
                children: vec![
 | 
			
		||||
                    TocItem {
 | 
			
		||||
                        title: "1. Purpose".to_string(),
 | 
			
		||||
                        file: "1-purpose.md".to_string(),
 | 
			
		||||
                        children: vec![],
 | 
			
		||||
                    },
 | 
			
		||||
                    TocItem {
 | 
			
		||||
                        title: "2. Tokenization Process".to_string(),
 | 
			
		||||
                        file: "2-tokenization-process.md".to_string(),
 | 
			
		||||
                        children: vec![],
 | 
			
		||||
                    },
 | 
			
		||||
                    TocItem {
 | 
			
		||||
                        title: "3. Revenue Sharing".to_string(),
 | 
			
		||||
                        file: "3-revenue-sharing.md".to_string(),
 | 
			
		||||
                        children: vec![],
 | 
			
		||||
                    },
 | 
			
		||||
                    TocItem {
 | 
			
		||||
                        title: "4. Governance".to_string(),
 | 
			
		||||
                        file: "4-governance.md".to_string(),
 | 
			
		||||
                        children: vec![],
 | 
			
		||||
                    },
 | 
			
		||||
                    TocItem {
 | 
			
		||||
                        title: "Appendix A: Properties".to_string(),
 | 
			
		||||
                        file: "appendix-a.md".to_string(),
 | 
			
		||||
                        children: vec![],
 | 
			
		||||
                    },
 | 
			
		||||
                    TocItem {
 | 
			
		||||
                        title: "Appendix B: Specifications".to_string(),
 | 
			
		||||
                        file: "appendix-b.md".to_string(),
 | 
			
		||||
                        children: vec![],
 | 
			
		||||
                    },
 | 
			
		||||
                    TocItem {
 | 
			
		||||
                        title: "Appendix C: Revenue Formula".to_string(),
 | 
			
		||||
                        file: "appendix-c.md".to_string(),
 | 
			
		||||
                        children: vec![],
 | 
			
		||||
                    },
 | 
			
		||||
                    TocItem {
 | 
			
		||||
                        title: "Appendix D: Governance Framework".to_string(),
 | 
			
		||||
                        file: "appendix-d.md".to_string(),
 | 
			
		||||
                        children: vec![],
 | 
			
		||||
                    },
 | 
			
		||||
                ],
 | 
			
		||||
            }
 | 
			
		||||
        ]);
 | 
			
		||||
        contract3.toc = Some(vec![TocItem {
 | 
			
		||||
            title: "Digital Asset Tokenization Agreement".to_string(),
 | 
			
		||||
            file: "cover.md".to_string(),
 | 
			
		||||
            children: vec![
 | 
			
		||||
                TocItem {
 | 
			
		||||
                    title: "1. Purpose".to_string(),
 | 
			
		||||
                    file: "1-purpose.md".to_string(),
 | 
			
		||||
                    children: vec![],
 | 
			
		||||
                },
 | 
			
		||||
                TocItem {
 | 
			
		||||
                    title: "2. Tokenization Process".to_string(),
 | 
			
		||||
                    file: "2-tokenization-process.md".to_string(),
 | 
			
		||||
                    children: vec![],
 | 
			
		||||
                },
 | 
			
		||||
                TocItem {
 | 
			
		||||
                    title: "3. Revenue Sharing".to_string(),
 | 
			
		||||
                    file: "3-revenue-sharing.md".to_string(),
 | 
			
		||||
                    children: vec![],
 | 
			
		||||
                },
 | 
			
		||||
                TocItem {
 | 
			
		||||
                    title: "4. Governance".to_string(),
 | 
			
		||||
                    file: "4-governance.md".to_string(),
 | 
			
		||||
                    children: vec![],
 | 
			
		||||
                },
 | 
			
		||||
                TocItem {
 | 
			
		||||
                    title: "Appendix A: Properties".to_string(),
 | 
			
		||||
                    file: "appendix-a.md".to_string(),
 | 
			
		||||
                    children: vec![],
 | 
			
		||||
                },
 | 
			
		||||
                TocItem {
 | 
			
		||||
                    title: "Appendix B: Specifications".to_string(),
 | 
			
		||||
                    file: "appendix-b.md".to_string(),
 | 
			
		||||
                    children: vec![],
 | 
			
		||||
                },
 | 
			
		||||
                TocItem {
 | 
			
		||||
                    title: "Appendix C: Revenue Formula".to_string(),
 | 
			
		||||
                    file: "appendix-c.md".to_string(),
 | 
			
		||||
                    children: vec![],
 | 
			
		||||
                },
 | 
			
		||||
                TocItem {
 | 
			
		||||
                    title: "Appendix D: Governance Framework".to_string(),
 | 
			
		||||
                    file: "appendix-d.md".to_string(),
 | 
			
		||||
                    children: vec![],
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
        }]);
 | 
			
		||||
        // No revision content for contract 3, content is in markdown files.
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Mock contract 4 - Rejected
 | 
			
		||||
        let mut contract4 = Contract {
 | 
			
		||||
            content_dir: None,
 | 
			
		||||
@@ -645,7 +850,7 @@ impl ContractController {
 | 
			
		||||
            revisions: Vec::new(),
 | 
			
		||||
            current_version: 1,
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Add signers to contract 4 with a rejection
 | 
			
		||||
        contract4.signers.push(ContractSigner {
 | 
			
		||||
            id: "signer-008".to_string(),
 | 
			
		||||
@@ -655,7 +860,7 @@ impl ContractController {
 | 
			
		||||
            signed_at: Some(Utc::now() - Duration::days(10)),
 | 
			
		||||
            comments: None,
 | 
			
		||||
        });
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        contract4.signers.push(ContractSigner {
 | 
			
		||||
            id: "signer-009".to_string(),
 | 
			
		||||
            name: "Dr. Amina Diallo".to_string(),
 | 
			
		||||
@@ -664,7 +869,7 @@ impl ContractController {
 | 
			
		||||
            signed_at: Some(Utc::now() - Duration::days(8)),
 | 
			
		||||
            comments: Some("Cannot agree to these terms due to privacy concerns. Please revise section 3.2 regarding data retention.".to_string()),
 | 
			
		||||
        });
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Add revisions to contract 4
 | 
			
		||||
        contract4.revisions.push(ContractRevision {
 | 
			
		||||
            version: 1,
 | 
			
		||||
@@ -673,7 +878,7 @@ impl ContractController {
 | 
			
		||||
            created_by: "Wei Chen".to_string(),
 | 
			
		||||
            comments: Some("Initial draft of the data sharing agreement.".to_string()),
 | 
			
		||||
        });
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Mock contract 5 - Active
 | 
			
		||||
        let mut contract5 = Contract {
 | 
			
		||||
            content_dir: None,
 | 
			
		||||
@@ -693,7 +898,7 @@ impl ContractController {
 | 
			
		||||
            revisions: Vec::new(),
 | 
			
		||||
            current_version: 2,
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Add signers to contract 5
 | 
			
		||||
        contract5.signers.push(ContractSigner {
 | 
			
		||||
            id: "signer-010".to_string(),
 | 
			
		||||
@@ -703,7 +908,7 @@ impl ContractController {
 | 
			
		||||
            signed_at: Some(Utc::now() - Duration::days(47)),
 | 
			
		||||
            comments: None,
 | 
			
		||||
        });
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        contract5.signers.push(ContractSigner {
 | 
			
		||||
            id: "signer-011".to_string(),
 | 
			
		||||
            name: "Li Wei".to_string(),
 | 
			
		||||
@@ -712,7 +917,7 @@ impl ContractController {
 | 
			
		||||
            signed_at: Some(Utc::now() - Duration::days(45)),
 | 
			
		||||
            comments: Some("Approved after legal review.".to_string()),
 | 
			
		||||
        });
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Add revisions to contract 5
 | 
			
		||||
        contract5.revisions.push(ContractRevision {
 | 
			
		||||
            version: 1,
 | 
			
		||||
@@ -721,7 +926,7 @@ impl ContractController {
 | 
			
		||||
            created_by: "Maya Rodriguez".to_string(),
 | 
			
		||||
            comments: Some("Initial draft of the identity verification service agreement.".to_string()),
 | 
			
		||||
        });
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        contract5.revisions.push(ContractRevision {
 | 
			
		||||
            version: 2,
 | 
			
		||||
            content: "<h1>Digital Identity Verification Service Agreement</h1><p>This Service Agreement (the \"Agreement\") is entered into between Zanzibar Digital Hub (\"Provider\") and the businesses listed in Appendix A (\"Clients\").</p><h2>1. Services</h2><p>Provider agrees to provide Clients with digital identity verification services as specified in Appendix B.</p><h2>2. Term</h2><p>This Agreement shall commence on the Effective Date and continue for a period of one (1) year unless terminated earlier in accordance with the terms herein.</p><h2>3. Fees</h2><p>Clients agree to pay Provider the fees set forth in Appendix C. All fees are due within thirty (30) days of invoice date.</p><h2>4. Service Level Agreement</h2><p>Provider shall maintain a service uptime of at least 99.9% as measured on a monthly basis.</p><h2>5. Compliance</h2><p>Provider shall comply with all applicable laws and regulations regarding identity verification and data protection, including but not limited to the Zanzibar Digital Economy Act.</p>".to_string(),
 | 
			
		||||
@@ -729,14 +934,14 @@ impl ContractController {
 | 
			
		||||
            created_by: "Maya Rodriguez".to_string(),
 | 
			
		||||
            comments: Some("Added compliance clause as requested by legal.".to_string()),
 | 
			
		||||
        });
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Add all contracts to the vector
 | 
			
		||||
        contracts.push(contract1);
 | 
			
		||||
        contracts.push(contract2);
 | 
			
		||||
        contracts.push(contract3);
 | 
			
		||||
        contracts.push(contract4);
 | 
			
		||||
        contracts.push(contract5);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        contracts
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,15 @@
 | 
			
		||||
use actix_web::{web, HttpResponse, Result};
 | 
			
		||||
use actix_web::HttpRequest;
 | 
			
		||||
use tera::{Context, Tera};
 | 
			
		||||
use chrono::{Utc, Duration};
 | 
			
		||||
use actix_web::{HttpResponse, Result, web};
 | 
			
		||||
use chrono::{Duration, Utc};
 | 
			
		||||
use serde::Deserialize;
 | 
			
		||||
use tera::{Context, Tera};
 | 
			
		||||
use uuid::Uuid;
 | 
			
		||||
 | 
			
		||||
use crate::models::asset::{Asset, AssetType, AssetStatus};
 | 
			
		||||
use crate::models::defi::{DefiPosition, DefiPositionType, DefiPositionStatus, ProvidingPosition, ReceivingPosition, DEFI_DB};
 | 
			
		||||
use crate::models::asset::Asset;
 | 
			
		||||
use crate::models::defi::{
 | 
			
		||||
    DEFI_DB, DefiPosition, DefiPositionStatus, DefiPositionType, ProvidingPosition,
 | 
			
		||||
    ReceivingPosition,
 | 
			
		||||
};
 | 
			
		||||
use crate::utils::render_template;
 | 
			
		||||
 | 
			
		||||
// Form structs for DeFi operations
 | 
			
		||||
@@ -26,6 +29,7 @@ pub struct ReceivingForm {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
pub struct LiquidityForm {
 | 
			
		||||
    pub first_token: String,
 | 
			
		||||
    pub first_amount: f64,
 | 
			
		||||
@@ -35,6 +39,7 @@ pub struct LiquidityForm {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
pub struct StakingForm {
 | 
			
		||||
    pub asset_id: String,
 | 
			
		||||
    pub amount: f64,
 | 
			
		||||
@@ -49,6 +54,7 @@ pub struct SwapForm {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
pub struct CollateralForm {
 | 
			
		||||
    pub asset_id: String,
 | 
			
		||||
    pub amount: f64,
 | 
			
		||||
@@ -63,29 +69,29 @@ impl DefiController {
 | 
			
		||||
    // Display the DeFi dashboard
 | 
			
		||||
    pub async fn index(tmpl: web::Data<Tera>, req: HttpRequest) -> Result<HttpResponse> {
 | 
			
		||||
        let mut context = Context::new();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        println!("DEBUG: Starting DeFi dashboard rendering");
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Get mock assets for the dropdown selectors
 | 
			
		||||
        let assets = Self::get_mock_assets();
 | 
			
		||||
        println!("DEBUG: Generated {} mock assets", assets.len());
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Add active_page for navigation highlighting
 | 
			
		||||
        context.insert("active_page", &"defi");
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Add DeFi stats
 | 
			
		||||
        let defi_stats = Self::get_defi_stats();
 | 
			
		||||
        context.insert("defi_stats", &serde_json::to_value(defi_stats).unwrap());
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Add recent assets for selection in forms
 | 
			
		||||
        let recent_assets: Vec<serde_json::Map<String, serde_json::Value>> = assets
 | 
			
		||||
            .iter()
 | 
			
		||||
            .take(5)
 | 
			
		||||
            .map(|a| Self::asset_to_json(a))
 | 
			
		||||
            .collect();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        context.insert("recent_assets", &recent_assets);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Get user's providing positions
 | 
			
		||||
        let db = DEFI_DB.lock().unwrap();
 | 
			
		||||
        let providing_positions = db.get_user_providing_positions("user123");
 | 
			
		||||
@@ -94,7 +100,7 @@ impl DefiController {
 | 
			
		||||
            .map(|p| serde_json::to_value(p).unwrap())
 | 
			
		||||
            .collect();
 | 
			
		||||
        context.insert("providing_positions", &providing_positions_json);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Get user's receiving positions
 | 
			
		||||
        let receiving_positions = db.get_user_receiving_positions("user123");
 | 
			
		||||
        let receiving_positions_json: Vec<serde_json::Value> = receiving_positions
 | 
			
		||||
@@ -102,27 +108,30 @@ impl DefiController {
 | 
			
		||||
            .map(|p| serde_json::to_value(p).unwrap())
 | 
			
		||||
            .collect();
 | 
			
		||||
        context.insert("receiving_positions", &receiving_positions_json);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Add success message if present in query params
 | 
			
		||||
        if let Some(success) = req.query_string().strip_prefix("success=") {
 | 
			
		||||
            let decoded = urlencoding::decode(success).unwrap_or_else(|_| success.into());
 | 
			
		||||
            context.insert("success_message", &decoded);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        println!("DEBUG: Rendering DeFi dashboard template");
 | 
			
		||||
        let response = render_template(&tmpl, "defi/index.html", &context);
 | 
			
		||||
        println!("DEBUG: Finished rendering DeFi dashboard template");
 | 
			
		||||
        response
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Process providing request
 | 
			
		||||
    pub async fn create_providing(_tmpl: web::Data<Tera>, form: web::Form<ProvidingForm>) -> Result<HttpResponse> {
 | 
			
		||||
    pub async fn create_providing(
 | 
			
		||||
        _tmpl: web::Data<Tera>,
 | 
			
		||||
        form: web::Form<ProvidingForm>,
 | 
			
		||||
    ) -> Result<HttpResponse> {
 | 
			
		||||
        println!("DEBUG: Processing providing request: {:?}", form);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Get the asset obligationails (in a real app, this would come from a database)
 | 
			
		||||
        let assets = Self::get_mock_assets();
 | 
			
		||||
        let asset = assets.iter().find(|a| a.id == form.asset_id);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        if let Some(asset) = asset {
 | 
			
		||||
            // Calculate profit share and return amount
 | 
			
		||||
            let profit_share = match form.duration {
 | 
			
		||||
@@ -133,9 +142,10 @@ impl DefiController {
 | 
			
		||||
                365 => 12.0,
 | 
			
		||||
                _ => 4.2, // Default to 30 days rate
 | 
			
		||||
            };
 | 
			
		||||
            
 | 
			
		||||
            let return_amount = form.amount + (form.amount * (profit_share / 100.0) * (form.duration as f64 / 365.0));
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            let return_amount = form.amount
 | 
			
		||||
                + (form.amount * (profit_share / 100.0) * (form.duration as f64 / 365.0));
 | 
			
		||||
 | 
			
		||||
            // Create a new providing position
 | 
			
		||||
            let providing_position = ProvidingPosition {
 | 
			
		||||
                base: DefiPosition {
 | 
			
		||||
@@ -156,17 +166,23 @@ impl DefiController {
 | 
			
		||||
                profit_share_earned: profit_share,
 | 
			
		||||
                return_amount,
 | 
			
		||||
            };
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            // Add the position to the database
 | 
			
		||||
            {
 | 
			
		||||
                let mut db = DEFI_DB.lock().unwrap();
 | 
			
		||||
                db.add_providing_position(providing_position);
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            // Redirect with success message
 | 
			
		||||
            let success_message = format!("Successfully provided {} {} for {} days", form.amount, asset.name, form.duration);
 | 
			
		||||
            let success_message = format!(
 | 
			
		||||
                "Successfully provided {} {} for {} days",
 | 
			
		||||
                form.amount, asset.name, form.duration
 | 
			
		||||
            );
 | 
			
		||||
            Ok(HttpResponse::SeeOther()
 | 
			
		||||
                .append_header(("Location", format!("/defi?success={}", urlencoding::encode(&success_message))))
 | 
			
		||||
                .append_header((
 | 
			
		||||
                    "Location",
 | 
			
		||||
                    format!("/defi?success={}", urlencoding::encode(&success_message)),
 | 
			
		||||
                ))
 | 
			
		||||
                .finish())
 | 
			
		||||
        } else {
 | 
			
		||||
            // Asset not found, redirect with error
 | 
			
		||||
@@ -175,15 +191,18 @@ impl DefiController {
 | 
			
		||||
                .finish())
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Process receiving request
 | 
			
		||||
    pub async fn create_receiving(_tmpl: web::Data<Tera>, form: web::Form<ReceivingForm>) -> Result<HttpResponse> {
 | 
			
		||||
    pub async fn create_receiving(
 | 
			
		||||
        _tmpl: web::Data<Tera>,
 | 
			
		||||
        form: web::Form<ReceivingForm>,
 | 
			
		||||
    ) -> Result<HttpResponse> {
 | 
			
		||||
        println!("DEBUG: Processing receiving request: {:?}", form);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Get the asset obligationails (in a real app, this would come from a database)
 | 
			
		||||
        let assets = Self::get_mock_assets();
 | 
			
		||||
        let collateral_asset = assets.iter().find(|a| a.id == form.collateral_asset_id);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        if let Some(collateral_asset) = collateral_asset {
 | 
			
		||||
            // Calculate profit share rate based on duration
 | 
			
		||||
            let profit_share_rate = match form.duration {
 | 
			
		||||
@@ -194,15 +213,17 @@ impl DefiController {
 | 
			
		||||
                365 => 10.0,
 | 
			
		||||
                _ => 5.0, // Default to 30 days rate
 | 
			
		||||
            };
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            // Calculate profit share and total to repay
 | 
			
		||||
            let profit_share = form.amount * (profit_share_rate / 100.0) * (form.duration as f64 / 365.0);
 | 
			
		||||
            let profit_share =
 | 
			
		||||
                form.amount * (profit_share_rate / 100.0) * (form.duration as f64 / 365.0);
 | 
			
		||||
            let total_to_repay = form.amount + profit_share;
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            // Calculate collateral value and ratio
 | 
			
		||||
            let collateral_value = form.collateral_amount * collateral_asset.latest_valuation().map_or(0.5, |v| v.value);
 | 
			
		||||
            let collateral_value = form.collateral_amount
 | 
			
		||||
                * collateral_asset.latest_valuation().map_or(0.5, |v| v.value);
 | 
			
		||||
            let collateral_ratio = (collateral_value / form.amount) * 100.0;
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            // Create a new receiving position
 | 
			
		||||
            let receiving_position = ReceivingPosition {
 | 
			
		||||
                base: DefiPosition {
 | 
			
		||||
@@ -230,18 +251,23 @@ impl DefiController {
 | 
			
		||||
                total_to_repay,
 | 
			
		||||
                collateral_ratio,
 | 
			
		||||
            };
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            // Add the position to the database
 | 
			
		||||
            {
 | 
			
		||||
                let mut db = DEFI_DB.lock().unwrap();
 | 
			
		||||
                db.add_receiving_position(receiving_position);
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            // Redirect with success message
 | 
			
		||||
            let success_message = format!("Successfully borrowed {} ZDFZ using {} {} as collateral", 
 | 
			
		||||
                form.amount, form.collateral_amount, collateral_asset.name);
 | 
			
		||||
            let success_message = format!(
 | 
			
		||||
                "Successfully borrowed {} ZDFZ using {} {} as collateral",
 | 
			
		||||
                form.amount, form.collateral_amount, collateral_asset.name
 | 
			
		||||
            );
 | 
			
		||||
            Ok(HttpResponse::SeeOther()
 | 
			
		||||
                .append_header(("Location", format!("/defi?success={}", urlencoding::encode(&success_message))))
 | 
			
		||||
                .append_header((
 | 
			
		||||
                    "Location",
 | 
			
		||||
                    format!("/defi?success={}", urlencoding::encode(&success_message)),
 | 
			
		||||
                ))
 | 
			
		||||
                .finish())
 | 
			
		||||
        } else {
 | 
			
		||||
            // Asset not found, redirect with error
 | 
			
		||||
@@ -250,116 +276,202 @@ impl DefiController {
 | 
			
		||||
                .finish())
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Process liquidity provision
 | 
			
		||||
    pub async fn add_liquidity(_tmpl: web::Data<Tera>, form: web::Form<LiquidityForm>) -> Result<HttpResponse> {
 | 
			
		||||
    pub async fn add_liquidity(
 | 
			
		||||
        _tmpl: web::Data<Tera>,
 | 
			
		||||
        form: web::Form<LiquidityForm>,
 | 
			
		||||
    ) -> Result<HttpResponse> {
 | 
			
		||||
        println!("DEBUG: Processing liquidity provision: {:?}", form);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // In a real application, this would add liquidity to a pool in the database
 | 
			
		||||
        // For now, we'll just redirect back to the DeFi dashboard with a success message
 | 
			
		||||
        
 | 
			
		||||
        let success_message = format!("Successfully added liquidity: {} {} and {} {}", 
 | 
			
		||||
            form.first_amount, form.first_token, form.second_amount, form.second_token);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        let success_message = format!(
 | 
			
		||||
            "Successfully added liquidity: {} {} and {} {}",
 | 
			
		||||
            form.first_amount, form.first_token, form.second_amount, form.second_token
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        Ok(HttpResponse::SeeOther()
 | 
			
		||||
            .append_header(("Location", format!("/defi?success={}", urlencoding::encode(&success_message))))
 | 
			
		||||
            .append_header((
 | 
			
		||||
                "Location",
 | 
			
		||||
                format!("/defi?success={}", urlencoding::encode(&success_message)),
 | 
			
		||||
            ))
 | 
			
		||||
            .finish())
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Process staking request
 | 
			
		||||
    pub async fn create_staking(_tmpl: web::Data<Tera>, form: web::Form<StakingForm>) -> Result<HttpResponse> {
 | 
			
		||||
    pub async fn create_staking(
 | 
			
		||||
        _tmpl: web::Data<Tera>,
 | 
			
		||||
        form: web::Form<StakingForm>,
 | 
			
		||||
    ) -> Result<HttpResponse> {
 | 
			
		||||
        println!("DEBUG: Processing staking request: {:?}", form);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // In a real application, this would create a staking position in the database
 | 
			
		||||
        // For now, we'll just redirect back to the DeFi dashboard with a success message
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        let success_message = format!("Successfully staked {} {}", form.amount, form.asset_id);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        Ok(HttpResponse::SeeOther()
 | 
			
		||||
            .append_header(("Location", format!("/defi?success={}", urlencoding::encode(&success_message))))
 | 
			
		||||
            .append_header((
 | 
			
		||||
                "Location",
 | 
			
		||||
                format!("/defi?success={}", urlencoding::encode(&success_message)),
 | 
			
		||||
            ))
 | 
			
		||||
            .finish())
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Process token swap
 | 
			
		||||
    pub async fn swap_tokens(_tmpl: web::Data<Tera>, form: web::Form<SwapForm>) -> Result<HttpResponse> {
 | 
			
		||||
    pub async fn swap_tokens(
 | 
			
		||||
        _tmpl: web::Data<Tera>,
 | 
			
		||||
        form: web::Form<SwapForm>,
 | 
			
		||||
    ) -> Result<HttpResponse> {
 | 
			
		||||
        println!("DEBUG: Processing token swap: {:?}", form);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // In a real application, this would perform a token swap in the database
 | 
			
		||||
        // For now, we'll just redirect back to the DeFi dashboard with a success message
 | 
			
		||||
        
 | 
			
		||||
        let success_message = format!("Successfully swapped {} {} to {}", 
 | 
			
		||||
            form.from_amount, form.from_token, form.to_token);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        let success_message = format!(
 | 
			
		||||
            "Successfully swapped {} {} to {}",
 | 
			
		||||
            form.from_amount, form.from_token, form.to_token
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        Ok(HttpResponse::SeeOther()
 | 
			
		||||
            .append_header(("Location", format!("/defi?success={}", urlencoding::encode(&success_message))))
 | 
			
		||||
            .append_header((
 | 
			
		||||
                "Location",
 | 
			
		||||
                format!("/defi?success={}", urlencoding::encode(&success_message)),
 | 
			
		||||
            ))
 | 
			
		||||
            .finish())
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Process collateral position creation
 | 
			
		||||
    pub async fn create_collateral(_tmpl: web::Data<Tera>, form: web::Form<CollateralForm>) -> Result<HttpResponse> {
 | 
			
		||||
    pub async fn create_collateral(
 | 
			
		||||
        _tmpl: web::Data<Tera>,
 | 
			
		||||
        form: web::Form<CollateralForm>,
 | 
			
		||||
    ) -> Result<HttpResponse> {
 | 
			
		||||
        println!("DEBUG: Processing collateral creation: {:?}", form);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // In a real application, this would create a collateral position in the database
 | 
			
		||||
        // For now, we'll just redirect back to the DeFi dashboard with a success message
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        let purpose_str = match form.purpose.as_str() {
 | 
			
		||||
            "funds" => "secure a funds",
 | 
			
		||||
            "synthetic" => "generate synthetic assets",
 | 
			
		||||
            "leverage" => "leverage trading",
 | 
			
		||||
            _ => "collateralization",
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        let success_message = format!("Successfully collateralized {} {} for {}", 
 | 
			
		||||
            form.amount, form.asset_id, purpose_str);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        let success_message = format!(
 | 
			
		||||
            "Successfully collateralized {} {} for {}",
 | 
			
		||||
            form.amount, form.asset_id, purpose_str
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        Ok(HttpResponse::SeeOther()
 | 
			
		||||
            .append_header(("Location", format!("/defi?success={}", urlencoding::encode(&success_message))))
 | 
			
		||||
            .append_header((
 | 
			
		||||
                "Location",
 | 
			
		||||
                format!("/defi?success={}", urlencoding::encode(&success_message)),
 | 
			
		||||
            ))
 | 
			
		||||
            .finish())
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Helper method to get DeFi statistics
 | 
			
		||||
    fn get_defi_stats() -> serde_json::Map<String, serde_json::Value> {
 | 
			
		||||
        let mut stats = serde_json::Map::new();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Handle Option<Number> by unwrapping with expect
 | 
			
		||||
        stats.insert("total_value_locked".to_string(), serde_json::Value::Number(serde_json::Number::from_f64(1250000.0).expect("Valid float")));
 | 
			
		||||
        stats.insert("providing_volume".to_string(), serde_json::Value::Number(serde_json::Number::from_f64(450000.0).expect("Valid float")));
 | 
			
		||||
        stats.insert("receiving_volume".to_string(), serde_json::Value::Number(serde_json::Number::from_f64(320000.0).expect("Valid float")));
 | 
			
		||||
        stats.insert("liquidity_pools_count".to_string(), serde_json::Value::Number(serde_json::Number::from(12)));
 | 
			
		||||
        stats.insert("active_stakers".to_string(), serde_json::Value::Number(serde_json::Number::from(156)));
 | 
			
		||||
        stats.insert("total_swap_volume".to_string(), serde_json::Value::Number(serde_json::Number::from_f64(780000.0).expect("Valid float")));
 | 
			
		||||
        
 | 
			
		||||
        stats.insert(
 | 
			
		||||
            "total_value_locked".to_string(),
 | 
			
		||||
            serde_json::Value::Number(
 | 
			
		||||
                serde_json::Number::from_f64(1250000.0).expect("Valid float"),
 | 
			
		||||
            ),
 | 
			
		||||
        );
 | 
			
		||||
        stats.insert(
 | 
			
		||||
            "providing_volume".to_string(),
 | 
			
		||||
            serde_json::Value::Number(serde_json::Number::from_f64(450000.0).expect("Valid float")),
 | 
			
		||||
        );
 | 
			
		||||
        stats.insert(
 | 
			
		||||
            "receiving_volume".to_string(),
 | 
			
		||||
            serde_json::Value::Number(serde_json::Number::from_f64(320000.0).expect("Valid float")),
 | 
			
		||||
        );
 | 
			
		||||
        stats.insert(
 | 
			
		||||
            "liquidity_pools_count".to_string(),
 | 
			
		||||
            serde_json::Value::Number(serde_json::Number::from(12)),
 | 
			
		||||
        );
 | 
			
		||||
        stats.insert(
 | 
			
		||||
            "active_stakers".to_string(),
 | 
			
		||||
            serde_json::Value::Number(serde_json::Number::from(156)),
 | 
			
		||||
        );
 | 
			
		||||
        stats.insert(
 | 
			
		||||
            "total_swap_volume".to_string(),
 | 
			
		||||
            serde_json::Value::Number(serde_json::Number::from_f64(780000.0).expect("Valid float")),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        stats
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Helper method to convert Asset to a JSON object for templates
 | 
			
		||||
    fn asset_to_json(asset: &Asset) -> serde_json::Map<String, serde_json::Value> {
 | 
			
		||||
        let mut map = serde_json::Map::new();
 | 
			
		||||
        
 | 
			
		||||
        map.insert("id".to_string(), serde_json::Value::String(asset.id.clone()));
 | 
			
		||||
        map.insert("name".to_string(), serde_json::Value::String(asset.name.clone()));
 | 
			
		||||
        map.insert("description".to_string(), serde_json::Value::String(asset.description.clone()));
 | 
			
		||||
        map.insert("asset_type".to_string(), serde_json::Value::String(asset.asset_type.as_str().to_string()));
 | 
			
		||||
        map.insert("status".to_string(), serde_json::Value::String(asset.status.as_str().to_string()));
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        map.insert(
 | 
			
		||||
            "id".to_string(),
 | 
			
		||||
            serde_json::Value::String(asset.id.clone()),
 | 
			
		||||
        );
 | 
			
		||||
        map.insert(
 | 
			
		||||
            "name".to_string(),
 | 
			
		||||
            serde_json::Value::String(asset.name.clone()),
 | 
			
		||||
        );
 | 
			
		||||
        map.insert(
 | 
			
		||||
            "description".to_string(),
 | 
			
		||||
            serde_json::Value::String(asset.description.clone()),
 | 
			
		||||
        );
 | 
			
		||||
        map.insert(
 | 
			
		||||
            "asset_type".to_string(),
 | 
			
		||||
            serde_json::Value::String(asset.asset_type.as_str().to_string()),
 | 
			
		||||
        );
 | 
			
		||||
        map.insert(
 | 
			
		||||
            "status".to_string(),
 | 
			
		||||
            serde_json::Value::String(asset.status.as_str().to_string()),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Add current valuation
 | 
			
		||||
        if let Some(latest) = asset.latest_valuation() {
 | 
			
		||||
            if let Some(num) = serde_json::Number::from_f64(latest.value) {
 | 
			
		||||
                map.insert("current_valuation".to_string(), serde_json::Value::Number(num));
 | 
			
		||||
                map.insert(
 | 
			
		||||
                    "current_valuation".to_string(),
 | 
			
		||||
                    serde_json::Value::Number(num),
 | 
			
		||||
                );
 | 
			
		||||
            } else {
 | 
			
		||||
                map.insert("current_valuation".to_string(), serde_json::Value::Number(serde_json::Number::from(0)));
 | 
			
		||||
                map.insert(
 | 
			
		||||
                    "current_valuation".to_string(),
 | 
			
		||||
                    serde_json::Value::Number(serde_json::Number::from(0)),
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
            map.insert("valuation_currency".to_string(), serde_json::Value::String(latest.currency.clone()));
 | 
			
		||||
            map.insert("valuation_date".to_string(), serde_json::Value::String(latest.date.format("%Y-%m-%d").to_string()));
 | 
			
		||||
            map.insert(
 | 
			
		||||
                "valuation_currency".to_string(),
 | 
			
		||||
                serde_json::Value::String(latest.currency.clone()),
 | 
			
		||||
            );
 | 
			
		||||
            map.insert(
 | 
			
		||||
                "valuation_date".to_string(),
 | 
			
		||||
                serde_json::Value::String(latest.date.format("%Y-%m-%d").to_string()),
 | 
			
		||||
            );
 | 
			
		||||
        } else {
 | 
			
		||||
            map.insert("current_valuation".to_string(), serde_json::Value::Number(serde_json::Number::from(0)));
 | 
			
		||||
            map.insert("valuation_currency".to_string(), serde_json::Value::String("USD".to_string()));
 | 
			
		||||
            map.insert("valuation_date".to_string(), serde_json::Value::String("N/A".to_string()));
 | 
			
		||||
            map.insert(
 | 
			
		||||
                "current_valuation".to_string(),
 | 
			
		||||
                serde_json::Value::Number(serde_json::Number::from(0)),
 | 
			
		||||
            );
 | 
			
		||||
            map.insert(
 | 
			
		||||
                "valuation_currency".to_string(),
 | 
			
		||||
                serde_json::Value::String("USD".to_string()),
 | 
			
		||||
            );
 | 
			
		||||
            map.insert(
 | 
			
		||||
                "valuation_date".to_string(),
 | 
			
		||||
                serde_json::Value::String("N/A".to_string()),
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        map
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Generate mock assets for testing
 | 
			
		||||
    fn get_mock_assets() -> Vec<Asset> {
 | 
			
		||||
        // Reuse the asset controller's mock data function
 | 
			
		||||
 
 | 
			
		||||
@@ -609,6 +609,7 @@ impl FlowController {
 | 
			
		||||
 | 
			
		||||
/// Form for creating a new flow
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
pub struct FlowForm {
 | 
			
		||||
    /// Flow name
 | 
			
		||||
    pub name: String,
 | 
			
		||||
@@ -620,6 +621,7 @@ pub struct FlowForm {
 | 
			
		||||
 | 
			
		||||
/// Form for marking a step as stuck
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
pub struct StuckForm {
 | 
			
		||||
    /// Reason for being stuck
 | 
			
		||||
    pub reason: String,
 | 
			
		||||
@@ -627,6 +629,7 @@ pub struct StuckForm {
 | 
			
		||||
 | 
			
		||||
/// Form for adding a log to a step
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
pub struct LogForm {
 | 
			
		||||
    /// Log message
 | 
			
		||||
    pub message: String,
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,7 @@ use chrono::prelude::*;
 | 
			
		||||
/// Controller for handling governance-related routes
 | 
			
		||||
pub struct GovernanceController;
 | 
			
		||||
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
impl GovernanceController {
 | 
			
		||||
    /// Helper function to get user from session
 | 
			
		||||
    /// For testing purposes, this will always return a mock user
 | 
			
		||||
@@ -607,6 +608,7 @@ pub struct ProposalForm {
 | 
			
		||||
 | 
			
		||||
/// Represents the data submitted in the vote form
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
pub struct VoteForm {
 | 
			
		||||
    /// Type of vote (yes, no, abstain)
 | 
			
		||||
    pub vote_type: String,
 | 
			
		||||
 
 | 
			
		||||
@@ -96,6 +96,7 @@ impl HomeController {
 | 
			
		||||
 | 
			
		||||
/// Represents the data submitted in the contact form
 | 
			
		||||
#[derive(Debug, serde::Deserialize)]
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
pub struct ContactForm {
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    pub email: String,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,11 @@
 | 
			
		||||
use actix_web::{web, HttpResponse, Result, http};
 | 
			
		||||
use tera::{Context, Tera};
 | 
			
		||||
use chrono::{Utc, Duration};
 | 
			
		||||
use actix_web::{HttpResponse, Result, http, web};
 | 
			
		||||
use chrono::{Duration, Utc};
 | 
			
		||||
use serde::Deserialize;
 | 
			
		||||
use uuid::Uuid;
 | 
			
		||||
use tera::{Context, Tera};
 | 
			
		||||
 | 
			
		||||
use crate::models::asset::{Asset, AssetType, AssetStatus};
 | 
			
		||||
use crate::models::marketplace::{Listing, ListingStatus, ListingType, Bid, BidStatus, MarketplaceStatistics};
 | 
			
		||||
use crate::controllers::asset::AssetController;
 | 
			
		||||
use crate::models::asset::{Asset, AssetStatus, AssetType};
 | 
			
		||||
use crate::models::marketplace::{Listing, ListingStatus, ListingType, MarketplaceStatistics};
 | 
			
		||||
use crate::utils::render_template;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
@@ -22,6 +21,7 @@ pub struct ListingForm {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
pub struct BidForm {
 | 
			
		||||
    pub amount: f64,
 | 
			
		||||
    pub currency: String,
 | 
			
		||||
@@ -38,30 +38,33 @@ impl MarketplaceController {
 | 
			
		||||
    // Display the marketplace dashboard
 | 
			
		||||
    pub async fn index(tmpl: web::Data<Tera>) -> Result<HttpResponse> {
 | 
			
		||||
        let mut context = Context::new();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        let listings = Self::get_mock_listings();
 | 
			
		||||
        let stats = MarketplaceStatistics::new(&listings);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Get featured listings (up to 4)
 | 
			
		||||
        let featured_listings: Vec<&Listing> = listings.iter()
 | 
			
		||||
        let featured_listings: Vec<&Listing> = listings
 | 
			
		||||
            .iter()
 | 
			
		||||
            .filter(|l| l.featured && l.status == ListingStatus::Active)
 | 
			
		||||
            .take(4)
 | 
			
		||||
            .collect();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Get recent listings (up to 8)
 | 
			
		||||
        let mut recent_listings: Vec<&Listing> = listings.iter()
 | 
			
		||||
        let mut recent_listings: Vec<&Listing> = listings
 | 
			
		||||
            .iter()
 | 
			
		||||
            .filter(|l| l.status == ListingStatus::Active)
 | 
			
		||||
            .collect();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Sort by created_at (newest first)
 | 
			
		||||
        recent_listings.sort_by(|a, b| b.created_at.cmp(&a.created_at));
 | 
			
		||||
        let recent_listings = recent_listings.into_iter().take(8).collect::<Vec<_>>();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Get recent sales (up to 5)
 | 
			
		||||
        let mut recent_sales: Vec<&Listing> = listings.iter()
 | 
			
		||||
        let mut recent_sales: Vec<&Listing> = listings
 | 
			
		||||
            .iter()
 | 
			
		||||
            .filter(|l| l.status == ListingStatus::Sold)
 | 
			
		||||
            .collect();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Sort by sold_at (newest first)
 | 
			
		||||
        recent_sales.sort_by(|a, b| {
 | 
			
		||||
            let a_sold = a.sold_at.unwrap_or(a.created_at);
 | 
			
		||||
@@ -69,88 +72,101 @@ impl MarketplaceController {
 | 
			
		||||
            b_sold.cmp(&a_sold)
 | 
			
		||||
        });
 | 
			
		||||
        let recent_sales = recent_sales.into_iter().take(5).collect::<Vec<_>>();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Add data to context
 | 
			
		||||
        context.insert("active_page", &"marketplace");
 | 
			
		||||
        context.insert("stats", &stats);
 | 
			
		||||
        context.insert("featured_listings", &featured_listings);
 | 
			
		||||
        context.insert("recent_listings", &recent_listings);
 | 
			
		||||
        context.insert("recent_sales", &recent_sales);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        render_template(&tmpl, "marketplace/index.html", &context)
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Display all marketplace listings
 | 
			
		||||
    pub async fn list_listings(tmpl: web::Data<Tera>) -> Result<HttpResponse> {
 | 
			
		||||
        let mut context = Context::new();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        let listings = Self::get_mock_listings();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Filter active listings
 | 
			
		||||
        let active_listings: Vec<&Listing> = listings.iter()
 | 
			
		||||
        let active_listings: Vec<&Listing> = listings
 | 
			
		||||
            .iter()
 | 
			
		||||
            .filter(|l| l.status == ListingStatus::Active)
 | 
			
		||||
            .collect();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        context.insert("active_page", &"marketplace");
 | 
			
		||||
        context.insert("listings", &active_listings);
 | 
			
		||||
        context.insert("listing_types", &[
 | 
			
		||||
            ListingType::FixedPrice.as_str(),
 | 
			
		||||
            ListingType::Auction.as_str(),
 | 
			
		||||
            ListingType::Exchange.as_str(),
 | 
			
		||||
        ]);
 | 
			
		||||
        context.insert("asset_types", &[
 | 
			
		||||
            AssetType::Token.as_str(),
 | 
			
		||||
            AssetType::Artwork.as_str(),
 | 
			
		||||
            AssetType::RealEstate.as_str(),
 | 
			
		||||
            AssetType::IntellectualProperty.as_str(),
 | 
			
		||||
            AssetType::Commodity.as_str(),
 | 
			
		||||
            AssetType::Share.as_str(),
 | 
			
		||||
            AssetType::Bond.as_str(),
 | 
			
		||||
            AssetType::Other.as_str(),
 | 
			
		||||
        ]);
 | 
			
		||||
        
 | 
			
		||||
        context.insert(
 | 
			
		||||
            "listing_types",
 | 
			
		||||
            &[
 | 
			
		||||
                ListingType::FixedPrice.as_str(),
 | 
			
		||||
                ListingType::Auction.as_str(),
 | 
			
		||||
                ListingType::Exchange.as_str(),
 | 
			
		||||
            ],
 | 
			
		||||
        );
 | 
			
		||||
        context.insert(
 | 
			
		||||
            "asset_types",
 | 
			
		||||
            &[
 | 
			
		||||
                AssetType::Token.as_str(),
 | 
			
		||||
                AssetType::Artwork.as_str(),
 | 
			
		||||
                AssetType::RealEstate.as_str(),
 | 
			
		||||
                AssetType::IntellectualProperty.as_str(),
 | 
			
		||||
                AssetType::Commodity.as_str(),
 | 
			
		||||
                AssetType::Share.as_str(),
 | 
			
		||||
                AssetType::Bond.as_str(),
 | 
			
		||||
                AssetType::Other.as_str(),
 | 
			
		||||
            ],
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        render_template(&tmpl, "marketplace/listings.html", &context)
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Display my listings
 | 
			
		||||
    pub async fn my_listings(tmpl: web::Data<Tera>) -> Result<HttpResponse> {
 | 
			
		||||
        let mut context = Context::new();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        let listings = Self::get_mock_listings();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Filter by current user (mock user ID)
 | 
			
		||||
        let user_id = "user-123";
 | 
			
		||||
        let my_listings: Vec<&Listing> = listings.iter()
 | 
			
		||||
            .filter(|l| l.seller_id == user_id)
 | 
			
		||||
            .collect();
 | 
			
		||||
        
 | 
			
		||||
        let my_listings: Vec<&Listing> =
 | 
			
		||||
            listings.iter().filter(|l| l.seller_id == user_id).collect();
 | 
			
		||||
 | 
			
		||||
        context.insert("active_page", &"marketplace");
 | 
			
		||||
        context.insert("listings", &my_listings);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        render_template(&tmpl, "marketplace/my_listings.html", &context)
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Display listing details
 | 
			
		||||
    pub async fn listing_detail(tmpl: web::Data<Tera>, path: web::Path<String>) -> Result<HttpResponse> {
 | 
			
		||||
    pub async fn listing_detail(
 | 
			
		||||
        tmpl: web::Data<Tera>,
 | 
			
		||||
        path: web::Path<String>,
 | 
			
		||||
    ) -> Result<HttpResponse> {
 | 
			
		||||
        let listing_id = path.into_inner();
 | 
			
		||||
        let mut context = Context::new();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        let listings = Self::get_mock_listings();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Find the listing
 | 
			
		||||
        let listing = listings.iter().find(|l| l.id == listing_id);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        if let Some(listing) = listing {
 | 
			
		||||
            // Get similar listings (same asset type, active)
 | 
			
		||||
            let similar_listings: Vec<&Listing> = listings.iter()
 | 
			
		||||
                .filter(|l| l.asset_type == listing.asset_type &&
 | 
			
		||||
                       l.status == ListingStatus::Active &&
 | 
			
		||||
                       l.id != listing.id)
 | 
			
		||||
            let similar_listings: Vec<&Listing> = listings
 | 
			
		||||
                .iter()
 | 
			
		||||
                .filter(|l| {
 | 
			
		||||
                    l.asset_type == listing.asset_type
 | 
			
		||||
                        && l.status == ListingStatus::Active
 | 
			
		||||
                        && l.id != listing.id
 | 
			
		||||
                })
 | 
			
		||||
                .take(4)
 | 
			
		||||
                .collect();
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            // Get highest bid amount and minimum bid for auction listings
 | 
			
		||||
            let (highest_bid_amount, minimum_bid) = if listing.listing_type == ListingType::Auction {
 | 
			
		||||
            let (highest_bid_amount, minimum_bid) = if listing.listing_type == ListingType::Auction
 | 
			
		||||
            {
 | 
			
		||||
                if let Some(bid) = listing.highest_bid() {
 | 
			
		||||
                    (Some(bid.amount), bid.amount + 1.0)
 | 
			
		||||
                } else {
 | 
			
		||||
@@ -159,74 +175,79 @@ impl MarketplaceController {
 | 
			
		||||
            } else {
 | 
			
		||||
                (None, 0.0)
 | 
			
		||||
            };
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            context.insert("active_page", &"marketplace");
 | 
			
		||||
            context.insert("listing", listing);
 | 
			
		||||
            context.insert("similar_listings", &similar_listings);
 | 
			
		||||
            context.insert("highest_bid_amount", &highest_bid_amount);
 | 
			
		||||
            context.insert("minimum_bid", &minimum_bid);
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            // Add current user info for bid/purchase forms
 | 
			
		||||
            let user_id = "user-123";
 | 
			
		||||
            let user_name = "Alice Hostly";
 | 
			
		||||
            context.insert("user_id", &user_id);
 | 
			
		||||
            context.insert("user_name", &user_name);
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            render_template(&tmpl, "marketplace/listing_detail.html", &context)
 | 
			
		||||
        } else {
 | 
			
		||||
            Ok(HttpResponse::NotFound().finish())
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Display create listing form
 | 
			
		||||
    pub async fn create_listing_form(tmpl: web::Data<Tera>) -> Result<HttpResponse> {
 | 
			
		||||
        let mut context = Context::new();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Get user's assets for selection
 | 
			
		||||
        let assets = AssetController::get_mock_assets();
 | 
			
		||||
        let user_id = "user-123"; // Mock user ID
 | 
			
		||||
        
 | 
			
		||||
        let user_assets: Vec<&Asset> = assets.iter()
 | 
			
		||||
 | 
			
		||||
        let user_assets: Vec<&Asset> = assets
 | 
			
		||||
            .iter()
 | 
			
		||||
            .filter(|a| a.owner_id == user_id && a.status == AssetStatus::Active)
 | 
			
		||||
            .collect();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        context.insert("active_page", &"marketplace");
 | 
			
		||||
        context.insert("assets", &user_assets);
 | 
			
		||||
        context.insert("listing_types", &[
 | 
			
		||||
            ListingType::FixedPrice.as_str(),
 | 
			
		||||
            ListingType::Auction.as_str(),
 | 
			
		||||
            ListingType::Exchange.as_str(),
 | 
			
		||||
        ]);
 | 
			
		||||
        
 | 
			
		||||
        context.insert(
 | 
			
		||||
            "listing_types",
 | 
			
		||||
            &[
 | 
			
		||||
                ListingType::FixedPrice.as_str(),
 | 
			
		||||
                ListingType::Auction.as_str(),
 | 
			
		||||
                ListingType::Exchange.as_str(),
 | 
			
		||||
            ],
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        render_template(&tmpl, "marketplace/create_listing.html", &context)
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Create a new listing
 | 
			
		||||
    pub async fn create_listing(
 | 
			
		||||
        tmpl: web::Data<Tera>,
 | 
			
		||||
        form: web::Form<ListingForm>,
 | 
			
		||||
    ) -> Result<HttpResponse> {
 | 
			
		||||
        let form = form.into_inner();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Get the asset details
 | 
			
		||||
        let assets = AssetController::get_mock_assets();
 | 
			
		||||
        let asset = assets.iter().find(|a| a.id == form.asset_id);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        if let Some(asset) = asset {
 | 
			
		||||
            // Process tags
 | 
			
		||||
            let tags = match form.tags {
 | 
			
		||||
                Some(tags_str) => tags_str.split(',')
 | 
			
		||||
                Some(tags_str) => tags_str
 | 
			
		||||
                    .split(',')
 | 
			
		||||
                    .map(|s| s.trim().to_string())
 | 
			
		||||
                    .filter(|s| !s.is_empty())
 | 
			
		||||
                    .collect(),
 | 
			
		||||
                None => Vec::new(),
 | 
			
		||||
            };
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            // Calculate expiration date if provided
 | 
			
		||||
            let expires_at = form.duration_days.map(|days| {
 | 
			
		||||
                Utc::now() + Duration::days(days as i64)
 | 
			
		||||
            });
 | 
			
		||||
            
 | 
			
		||||
            let expires_at = form
 | 
			
		||||
                .duration_days
 | 
			
		||||
                .map(|days| Utc::now() + Duration::days(days as i64));
 | 
			
		||||
 | 
			
		||||
            // Parse listing type
 | 
			
		||||
            let listing_type = match form.listing_type.as_str() {
 | 
			
		||||
                "Fixed Price" => ListingType::FixedPrice,
 | 
			
		||||
@@ -234,11 +255,11 @@ impl MarketplaceController {
 | 
			
		||||
                "Exchange" => ListingType::Exchange,
 | 
			
		||||
                _ => ListingType::FixedPrice,
 | 
			
		||||
            };
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            // Mock user data
 | 
			
		||||
            let user_id = "user-123";
 | 
			
		||||
            let user_name = "Alice Hostly";
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            // Create the listing
 | 
			
		||||
            let _listing = Listing::new(
 | 
			
		||||
                form.title,
 | 
			
		||||
@@ -255,9 +276,9 @@ impl MarketplaceController {
 | 
			
		||||
                tags,
 | 
			
		||||
                asset.image_url.clone(),
 | 
			
		||||
            );
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            // In a real application, we would save the listing to a database here
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            // Redirect to the marketplace
 | 
			
		||||
            Ok(HttpResponse::SeeOther()
 | 
			
		||||
                .insert_header((http::header::LOCATION, "/marketplace"))
 | 
			
		||||
@@ -267,94 +288,101 @@ impl MarketplaceController {
 | 
			
		||||
            let mut context = Context::new();
 | 
			
		||||
            context.insert("active_page", &"marketplace");
 | 
			
		||||
            context.insert("error", &"Asset not found");
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            render_template(&tmpl, "marketplace/create_listing.html", &context)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Submit a bid on an auction listing
 | 
			
		||||
    #[allow(dead_code)]
 | 
			
		||||
    pub async fn submit_bid(
 | 
			
		||||
        tmpl: web::Data<Tera>,
 | 
			
		||||
        _tmpl: web::Data<Tera>,
 | 
			
		||||
        path: web::Path<String>,
 | 
			
		||||
        form: web::Form<BidForm>,
 | 
			
		||||
        _form: web::Form<BidForm>,
 | 
			
		||||
    ) -> Result<HttpResponse> {
 | 
			
		||||
        let listing_id = path.into_inner();
 | 
			
		||||
        let form = form.into_inner();
 | 
			
		||||
        
 | 
			
		||||
        let _form = _form.into_inner();
 | 
			
		||||
 | 
			
		||||
        // In a real application, we would:
 | 
			
		||||
        // 1. Find the listing in the database
 | 
			
		||||
        // 2. Validate the bid
 | 
			
		||||
        // 3. Create the bid
 | 
			
		||||
        // 4. Save it to the database
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // For now, we'll just redirect back to the listing
 | 
			
		||||
        Ok(HttpResponse::SeeOther()
 | 
			
		||||
            .insert_header((http::header::LOCATION, format!("/marketplace/{}", listing_id)))
 | 
			
		||||
            .insert_header((
 | 
			
		||||
                http::header::LOCATION,
 | 
			
		||||
                format!("/marketplace/{}", listing_id),
 | 
			
		||||
            ))
 | 
			
		||||
            .finish())
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Purchase a fixed-price listing
 | 
			
		||||
    pub async fn purchase_listing(
 | 
			
		||||
        tmpl: web::Data<Tera>,
 | 
			
		||||
        _tmpl: web::Data<Tera>,
 | 
			
		||||
        path: web::Path<String>,
 | 
			
		||||
        form: web::Form<PurchaseForm>,
 | 
			
		||||
    ) -> Result<HttpResponse> {
 | 
			
		||||
        let listing_id = path.into_inner();
 | 
			
		||||
        let form = form.into_inner();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        if !form.agree_to_terms {
 | 
			
		||||
            // User must agree to terms
 | 
			
		||||
            return Ok(HttpResponse::SeeOther()
 | 
			
		||||
                .insert_header((http::header::LOCATION, format!("/marketplace/{}", listing_id)))
 | 
			
		||||
                .insert_header((
 | 
			
		||||
                    http::header::LOCATION,
 | 
			
		||||
                    format!("/marketplace/{}", listing_id),
 | 
			
		||||
                ))
 | 
			
		||||
                .finish());
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // In a real application, we would:
 | 
			
		||||
        // 1. Find the listing in the database
 | 
			
		||||
        // 2. Validate the purchase
 | 
			
		||||
        // 3. Process the transaction
 | 
			
		||||
        // 4. Update the listing status
 | 
			
		||||
        // 5. Transfer the asset
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // For now, we'll just redirect to the marketplace
 | 
			
		||||
        Ok(HttpResponse::SeeOther()
 | 
			
		||||
            .insert_header((http::header::LOCATION, "/marketplace"))
 | 
			
		||||
            .finish())
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Cancel a listing
 | 
			
		||||
    pub async fn cancel_listing(
 | 
			
		||||
        tmpl: web::Data<Tera>,
 | 
			
		||||
        _tmpl: web::Data<Tera>,
 | 
			
		||||
        path: web::Path<String>,
 | 
			
		||||
    ) -> Result<HttpResponse> {
 | 
			
		||||
        let _listing_id = path.into_inner();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // In a real application, we would:
 | 
			
		||||
        // 1. Find the listing in the database
 | 
			
		||||
        // 2. Validate that the current user is the seller
 | 
			
		||||
        // 3. Update the listing status
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // For now, we'll just redirect to my listings
 | 
			
		||||
        Ok(HttpResponse::SeeOther()
 | 
			
		||||
            .insert_header((http::header::LOCATION, "/marketplace/my"))
 | 
			
		||||
            .finish())
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Generate mock listings for development
 | 
			
		||||
    pub fn get_mock_listings() -> Vec<Listing> {
 | 
			
		||||
        let assets = AssetController::get_mock_assets();
 | 
			
		||||
        let mut listings = Vec::new();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Mock user data
 | 
			
		||||
        let user_ids = vec!["user-123", "user-456", "user-789"];
 | 
			
		||||
        let user_names = vec!["Alice Hostly", "Ethan Cloudman", "Priya Servera"];
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Create some fixed price listings
 | 
			
		||||
        for i in 0..6 {
 | 
			
		||||
            let asset_index = i % assets.len();
 | 
			
		||||
            let asset = &assets[asset_index];
 | 
			
		||||
            let user_index = i % user_ids.len();
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            let price = match asset.asset_type {
 | 
			
		||||
                AssetType::Token => 50.0 + (i as f64 * 10.0),
 | 
			
		||||
                AssetType::Artwork => 500.0 + (i as f64 * 100.0),
 | 
			
		||||
@@ -365,10 +393,13 @@ impl MarketplaceController {
 | 
			
		||||
                AssetType::Bond => 1500.0 + (i as f64 * 300.0),
 | 
			
		||||
                AssetType::Other => 800.0 + (i as f64 * 150.0),
 | 
			
		||||
            };
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            let mut listing = Listing::new(
 | 
			
		||||
                format!("{} for Sale", asset.name),
 | 
			
		||||
                format!("This is a great opportunity to own {}. {}", asset.name, asset.description),
 | 
			
		||||
                format!(
 | 
			
		||||
                    "This is a great opportunity to own {}. {}",
 | 
			
		||||
                    asset.name, asset.description
 | 
			
		||||
                ),
 | 
			
		||||
                asset.id.clone(),
 | 
			
		||||
                asset.name.clone(),
 | 
			
		||||
                asset.asset_type.clone(),
 | 
			
		||||
@@ -381,21 +412,21 @@ impl MarketplaceController {
 | 
			
		||||
                vec!["digital".to_string(), "asset".to_string()],
 | 
			
		||||
                asset.image_url.clone(),
 | 
			
		||||
            );
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            // Make some listings featured
 | 
			
		||||
            if i % 5 == 0 {
 | 
			
		||||
                listing.set_featured(true);
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            listings.push(listing);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Create some auction listings
 | 
			
		||||
        for i in 0..4 {
 | 
			
		||||
            let asset_index = (i + 6) % assets.len();
 | 
			
		||||
            let asset = &assets[asset_index];
 | 
			
		||||
            let user_index = i % user_ids.len();
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            let starting_price = match asset.asset_type {
 | 
			
		||||
                AssetType::Token => 40.0 + (i as f64 * 5.0),
 | 
			
		||||
                AssetType::Artwork => 400.0 + (i as f64 * 50.0),
 | 
			
		||||
@@ -406,7 +437,7 @@ impl MarketplaceController {
 | 
			
		||||
                AssetType::Bond => 1200.0 + (i as f64 * 250.0),
 | 
			
		||||
                AssetType::Other => 600.0 + (i as f64 * 120.0),
 | 
			
		||||
            };
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            let mut listing = Listing::new(
 | 
			
		||||
                format!("Auction: {}", asset.name),
 | 
			
		||||
                format!("Bid on this amazing {}. {}", asset.name, asset.description),
 | 
			
		||||
@@ -422,12 +453,13 @@ impl MarketplaceController {
 | 
			
		||||
                vec!["auction".to_string(), "bidding".to_string()],
 | 
			
		||||
                asset.image_url.clone(),
 | 
			
		||||
            );
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            // Add some bids to the auctions
 | 
			
		||||
            let num_bids = 2 + (i % 3);
 | 
			
		||||
            for j in 0..num_bids {
 | 
			
		||||
                let bidder_index = (j + 1) % user_ids.len();
 | 
			
		||||
                if bidder_index != user_index {  // Ensure seller isn't bidding
 | 
			
		||||
                if bidder_index != user_index {
 | 
			
		||||
                    // Ensure seller isn't bidding
 | 
			
		||||
                    let bid_amount = starting_price * (1.0 + (0.1 * (j + 1) as f64));
 | 
			
		||||
                    let _ = listing.add_bid(
 | 
			
		||||
                        user_ids[bidder_index].to_string(),
 | 
			
		||||
@@ -437,21 +469,21 @@ impl MarketplaceController {
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            // Make some listings featured
 | 
			
		||||
            if i % 3 == 0 {
 | 
			
		||||
                listing.set_featured(true);
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            listings.push(listing);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Create some exchange listings
 | 
			
		||||
        for i in 0..3 {
 | 
			
		||||
            let asset_index = (i + 10) % assets.len();
 | 
			
		||||
            let asset = &assets[asset_index];
 | 
			
		||||
            let user_index = i % user_ids.len();
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            let value = match asset.asset_type {
 | 
			
		||||
                AssetType::Token => 60.0 + (i as f64 * 15.0),
 | 
			
		||||
                AssetType::Artwork => 600.0 + (i as f64 * 150.0),
 | 
			
		||||
@@ -462,33 +494,36 @@ impl MarketplaceController {
 | 
			
		||||
                AssetType::Bond => 1800.0 + (i as f64 * 350.0),
 | 
			
		||||
                AssetType::Other => 1000.0 + (i as f64 * 200.0),
 | 
			
		||||
            };
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            let listing = Listing::new(
 | 
			
		||||
                format!("Trade: {}", asset.name),
 | 
			
		||||
                format!("Looking to exchange {} for another asset of similar value. Interested in NFTs and tokens.", asset.name),
 | 
			
		||||
                format!(
 | 
			
		||||
                    "Looking to exchange {} for another asset of similar value. Interested in NFTs and tokens.",
 | 
			
		||||
                    asset.name
 | 
			
		||||
                ),
 | 
			
		||||
                asset.id.clone(),
 | 
			
		||||
                asset.name.clone(),
 | 
			
		||||
                asset.asset_type.clone(),
 | 
			
		||||
                user_ids[user_index].to_string(),
 | 
			
		||||
                user_names[user_index].to_string(),
 | 
			
		||||
                value,  // Estimated value for exchange
 | 
			
		||||
                value, // Estimated value for exchange
 | 
			
		||||
                "USD".to_string(),
 | 
			
		||||
                ListingType::Exchange,
 | 
			
		||||
                Some(Utc::now() + Duration::days(60)),
 | 
			
		||||
                vec!["exchange".to_string(), "trade".to_string()],
 | 
			
		||||
                asset.image_url.clone(),
 | 
			
		||||
            );
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            listings.push(listing);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Create some sold listings
 | 
			
		||||
        for i in 0..5 {
 | 
			
		||||
            let asset_index = (i + 13) % assets.len();
 | 
			
		||||
            let asset = &assets[asset_index];
 | 
			
		||||
            let seller_index = i % user_ids.len();
 | 
			
		||||
            let buyer_index = (i + 1) % user_ids.len();
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            let price = match asset.asset_type {
 | 
			
		||||
                AssetType::Token => 55.0 + (i as f64 * 12.0),
 | 
			
		||||
                AssetType::Artwork => 550.0 + (i as f64 * 120.0),
 | 
			
		||||
@@ -499,9 +534,9 @@ impl MarketplaceController {
 | 
			
		||||
                AssetType::Bond => 1650.0 + (i as f64 * 330.0),
 | 
			
		||||
                AssetType::Other => 900.0 + (i as f64 * 180.0),
 | 
			
		||||
            };
 | 
			
		||||
            
 | 
			
		||||
            let sale_price = price * 0.95;  // Slight discount on sale
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            let sale_price = price * 0.95; // Slight discount on sale
 | 
			
		||||
 | 
			
		||||
            let mut listing = Listing::new(
 | 
			
		||||
                format!("{} - SOLD", asset.name),
 | 
			
		||||
                format!("This {} was sold recently.", asset.name),
 | 
			
		||||
@@ -517,27 +552,27 @@ impl MarketplaceController {
 | 
			
		||||
                vec!["sold".to_string()],
 | 
			
		||||
                asset.image_url.clone(),
 | 
			
		||||
            );
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            // Mark as sold
 | 
			
		||||
            let _ = listing.mark_as_sold(
 | 
			
		||||
                user_ids[buyer_index].to_string(),
 | 
			
		||||
                user_names[buyer_index].to_string(),
 | 
			
		||||
                sale_price,
 | 
			
		||||
            );
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            // Set sold date to be sometime in the past
 | 
			
		||||
            let days_ago = i as i64 + 1;
 | 
			
		||||
            listing.sold_at = Some(Utc::now() - Duration::days(days_ago));
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            listings.push(listing);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Create a few cancelled listings
 | 
			
		||||
        for i in 0..2 {
 | 
			
		||||
            let asset_index = (i + 18) % assets.len();
 | 
			
		||||
            let asset = &assets[asset_index];
 | 
			
		||||
            let user_index = i % user_ids.len();
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            let price = match asset.asset_type {
 | 
			
		||||
                AssetType::Token => 45.0 + (i as f64 * 8.0),
 | 
			
		||||
                AssetType::Artwork => 450.0 + (i as f64 * 80.0),
 | 
			
		||||
@@ -548,7 +583,7 @@ impl MarketplaceController {
 | 
			
		||||
                AssetType::Bond => 1350.0 + (i as f64 * 270.0),
 | 
			
		||||
                AssetType::Other => 750.0 + (i as f64 * 150.0),
 | 
			
		||||
            };
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            let mut listing = Listing::new(
 | 
			
		||||
                format!("{} - Cancelled", asset.name),
 | 
			
		||||
                format!("This listing for {} was cancelled.", asset.name),
 | 
			
		||||
@@ -564,13 +599,13 @@ impl MarketplaceController {
 | 
			
		||||
                vec!["cancelled".to_string()],
 | 
			
		||||
                asset.image_url.clone(),
 | 
			
		||||
            );
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            // Cancel the listing
 | 
			
		||||
            let _ = listing.cancel();
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            listings.push(listing);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        listings
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -112,6 +112,7 @@ pub struct Asset {
 | 
			
		||||
    pub external_url: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
impl Asset {
 | 
			
		||||
    /// Creates a new asset
 | 
			
		||||
    pub fn new(
 | 
			
		||||
 
 | 
			
		||||
@@ -85,6 +85,7 @@ pub struct ContractSigner {
 | 
			
		||||
    pub comments: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
impl ContractSigner {
 | 
			
		||||
    /// Creates a new contract signer
 | 
			
		||||
    pub fn new(name: String, email: String) -> Self {
 | 
			
		||||
@@ -123,6 +124,7 @@ pub struct ContractRevision {
 | 
			
		||||
    pub comments: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
impl ContractRevision {
 | 
			
		||||
    /// Creates a new contract revision
 | 
			
		||||
    pub fn new(version: u32, content: String, created_by: String, comments: Option<String>) -> Self {
 | 
			
		||||
@@ -166,6 +168,7 @@ pub struct Contract {
 | 
			
		||||
    pub toc: Option<Vec<TocItem>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
impl Contract {
 | 
			
		||||
    /// Creates a new contract
 | 
			
		||||
    pub fn new(title: String, description: String, contract_type: ContractType, created_by: String, organization_id: Option<String>) -> Self {
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,7 @@ pub enum DefiPositionStatus {
 | 
			
		||||
    Cancelled
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
impl DefiPositionStatus {
 | 
			
		||||
    pub fn as_str(&self) -> &str {
 | 
			
		||||
        match self {
 | 
			
		||||
@@ -35,6 +36,7 @@ pub enum DefiPositionType {
 | 
			
		||||
    Collateral,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
impl DefiPositionType {
 | 
			
		||||
    pub fn as_str(&self) -> &str {
 | 
			
		||||
        match self {
 | 
			
		||||
@@ -95,6 +97,7 @@ pub struct DefiDatabase {
 | 
			
		||||
    receiving_positions: HashMap<String, ReceivingPosition>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
impl DefiDatabase {
 | 
			
		||||
    pub fn new() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
 
 | 
			
		||||
@@ -110,6 +110,7 @@ pub struct FlowStep {
 | 
			
		||||
    pub logs: Vec<FlowLog>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
impl FlowStep {
 | 
			
		||||
    /// Creates a new flow step
 | 
			
		||||
    pub fn new(name: String, description: String, order: u32) -> Self {
 | 
			
		||||
@@ -189,6 +190,7 @@ pub struct FlowLog {
 | 
			
		||||
    pub timestamp: DateTime<Utc>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
impl FlowLog {
 | 
			
		||||
    /// Creates a new flow log
 | 
			
		||||
    pub fn new(message: String) -> Self {
 | 
			
		||||
@@ -231,6 +233,7 @@ pub struct Flow {
 | 
			
		||||
    pub current_step: Option<FlowStep>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
impl Flow {
 | 
			
		||||
    /// Creates a new flow
 | 
			
		||||
    pub fn new(name: &str, description: &str, flow_type: FlowType, owner_id: &str, owner_name: &str) -> Self {
 | 
			
		||||
 
 | 
			
		||||
@@ -75,6 +75,7 @@ pub struct Proposal {
 | 
			
		||||
    pub voting_ends_at: Option<DateTime<Utc>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
impl Proposal {
 | 
			
		||||
    /// Creates a new proposal
 | 
			
		||||
    pub fn new(creator_id: i32, creator_name: String, title: String, description: String) -> Self {
 | 
			
		||||
@@ -140,6 +141,7 @@ pub struct Vote {
 | 
			
		||||
    pub updated_at: DateTime<Utc>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
impl Vote {
 | 
			
		||||
    /// Creates a new vote
 | 
			
		||||
    pub fn new(proposal_id: String, voter_id: i32, voter_name: String, vote_type: VoteType, comment: Option<String>) -> Self {
 | 
			
		||||
@@ -200,6 +202,7 @@ pub struct VotingResults {
 | 
			
		||||
    pub total_votes: usize,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
impl VotingResults {
 | 
			
		||||
    /// Creates a new empty voting results object
 | 
			
		||||
    pub fn new(proposal_id: String) -> Self {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
use crate::models::asset::AssetType;
 | 
			
		||||
use chrono::{DateTime, Utc};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use uuid::Uuid;
 | 
			
		||||
use crate::models::asset::{Asset, AssetType};
 | 
			
		||||
 | 
			
		||||
/// Status of a marketplace listing
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
@@ -12,6 +12,7 @@ pub enum ListingStatus {
 | 
			
		||||
    Expired,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
impl ListingStatus {
 | 
			
		||||
    pub fn as_str(&self) -> &str {
 | 
			
		||||
        match self {
 | 
			
		||||
@@ -63,6 +64,7 @@ pub enum BidStatus {
 | 
			
		||||
    Cancelled,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
impl BidStatus {
 | 
			
		||||
    pub fn as_str(&self) -> &str {
 | 
			
		||||
        match self {
 | 
			
		||||
@@ -103,6 +105,7 @@ pub struct Listing {
 | 
			
		||||
    pub image_url: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
impl Listing {
 | 
			
		||||
    /// Creates a new listing
 | 
			
		||||
    pub fn new(
 | 
			
		||||
@@ -150,7 +153,13 @@ impl Listing {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Adds a bid to the listing
 | 
			
		||||
    pub fn add_bid(&mut self, bidder_id: String, bidder_name: String, amount: f64, currency: String) -> Result<(), String> {
 | 
			
		||||
    pub fn add_bid(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        bidder_id: String,
 | 
			
		||||
        bidder_name: String,
 | 
			
		||||
        amount: f64,
 | 
			
		||||
        currency: String,
 | 
			
		||||
    ) -> Result<(), String> {
 | 
			
		||||
        if self.status != ListingStatus::Active {
 | 
			
		||||
            return Err("Listing is not active".to_string());
 | 
			
		||||
        }
 | 
			
		||||
@@ -160,7 +169,10 @@ impl Listing {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if currency != self.currency {
 | 
			
		||||
            return Err(format!("Currency mismatch: expected {}, got {}", self.currency, currency));
 | 
			
		||||
            return Err(format!(
 | 
			
		||||
                "Currency mismatch: expected {}, got {}",
 | 
			
		||||
                self.currency, currency
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Check if bid amount is higher than current highest bid or starting price
 | 
			
		||||
@@ -193,13 +205,19 @@ impl Listing {
 | 
			
		||||
 | 
			
		||||
    /// Gets the highest bid on the listing
 | 
			
		||||
    pub fn highest_bid(&self) -> Option<&Bid> {
 | 
			
		||||
        self.bids.iter()
 | 
			
		||||
        self.bids
 | 
			
		||||
            .iter()
 | 
			
		||||
            .filter(|b| b.status == BidStatus::Active)
 | 
			
		||||
            .max_by(|a, b| a.amount.partial_cmp(&b.amount).unwrap())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Marks the listing as sold
 | 
			
		||||
    pub fn mark_as_sold(&mut self, buyer_id: String, buyer_name: String, sale_price: f64) -> Result<(), String> {
 | 
			
		||||
    pub fn mark_as_sold(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        buyer_id: String,
 | 
			
		||||
        buyer_name: String,
 | 
			
		||||
        sale_price: f64,
 | 
			
		||||
    ) -> Result<(), String> {
 | 
			
		||||
        if self.status != ListingStatus::Active {
 | 
			
		||||
            return Err("Listing is not active".to_string());
 | 
			
		||||
        }
 | 
			
		||||
@@ -257,11 +275,13 @@ impl MarketplaceStatistics {
 | 
			
		||||
        let mut listings_by_type = std::collections::HashMap::new();
 | 
			
		||||
        let mut sales_by_asset_type = std::collections::HashMap::new();
 | 
			
		||||
 | 
			
		||||
        let active_listings = listings.iter()
 | 
			
		||||
        let active_listings = listings
 | 
			
		||||
            .iter()
 | 
			
		||||
            .filter(|l| l.status == ListingStatus::Active)
 | 
			
		||||
            .count();
 | 
			
		||||
 | 
			
		||||
        let sold_listings = listings.iter()
 | 
			
		||||
        let sold_listings = listings
 | 
			
		||||
            .iter()
 | 
			
		||||
            .filter(|l| l.status == ListingStatus::Sold)
 | 
			
		||||
            .count();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,16 @@
 | 
			
		||||
// Export models
 | 
			
		||||
pub mod user;
 | 
			
		||||
pub mod ticket;
 | 
			
		||||
pub mod calendar;
 | 
			
		||||
pub mod governance;
 | 
			
		||||
pub mod flow;
 | 
			
		||||
pub mod contract;
 | 
			
		||||
pub mod asset;
 | 
			
		||||
pub mod marketplace;
 | 
			
		||||
pub mod calendar;
 | 
			
		||||
pub mod contract;
 | 
			
		||||
pub mod defi;
 | 
			
		||||
pub mod flow;
 | 
			
		||||
pub mod governance;
 | 
			
		||||
pub mod marketplace;
 | 
			
		||||
pub mod ticket;
 | 
			
		||||
pub mod user;
 | 
			
		||||
 | 
			
		||||
// Re-export models for easier imports
 | 
			
		||||
pub use user::User;
 | 
			
		||||
pub use ticket::{Ticket, TicketComment, TicketStatus, TicketPriority};
 | 
			
		||||
pub use calendar::{CalendarEvent, CalendarViewMode};
 | 
			
		||||
pub use marketplace::{Listing, ListingStatus, ListingType, Bid, BidStatus, MarketplaceStatistics};
 | 
			
		||||
pub use defi::{DefiPosition, DefiPositionType, DefiPositionStatus, ProvidingPosition, ReceivingPosition, DEFI_DB, initialize_mock_data};
 | 
			
		||||
pub use defi::initialize_mock_data;
 | 
			
		||||
pub use ticket::{Ticket, TicketComment, TicketPriority, TicketStatus};
 | 
			
		||||
pub use user::User;
 | 
			
		||||
 
 | 
			
		||||
@@ -76,6 +76,7 @@ pub struct Ticket {
 | 
			
		||||
    pub assigned_to: Option<i32>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
impl Ticket {
 | 
			
		||||
    /// Creates a new ticket
 | 
			
		||||
    pub fn new(user_id: i32, title: String, description: String, priority: TicketPriority) -> Self {
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ use bcrypt::{hash, verify, DEFAULT_COST};
 | 
			
		||||
 | 
			
		||||
/// Represents a user in the system
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
pub struct User {
 | 
			
		||||
    /// Unique identifier for the user
 | 
			
		||||
    pub id: Option<i32>,
 | 
			
		||||
@@ -31,6 +32,7 @@ pub enum UserRole {
 | 
			
		||||
    Admin,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
impl User {
 | 
			
		||||
    /// Creates a new user with default values
 | 
			
		||||
    pub fn new(name: String, email: String) -> Self {
 | 
			
		||||
@@ -125,6 +127,7 @@ impl User {
 | 
			
		||||
 | 
			
		||||
/// Represents user login credentials
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
pub struct LoginCredentials {
 | 
			
		||||
    pub email: String,
 | 
			
		||||
    pub password: String,
 | 
			
		||||
@@ -132,6 +135,7 @@ pub struct LoginCredentials {
 | 
			
		||||
 | 
			
		||||
/// Represents user registration data
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
pub struct RegistrationData {
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    pub email: String,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
use actix_web::{error, Error, HttpResponse};
 | 
			
		||||
use actix_web::{Error, HttpResponse};
 | 
			
		||||
use chrono::{DateTime, Utc};
 | 
			
		||||
use tera::{self, Context, Function, Tera, Value};
 | 
			
		||||
use std::error::Error as StdError;
 | 
			
		||||
use tera::{self, Context, Function, Tera, Value};
 | 
			
		||||
 | 
			
		||||
// Export modules
 | 
			
		||||
pub mod redis_service;
 | 
			
		||||
@@ -11,6 +11,7 @@ pub use redis_service::RedisCalendarService;
 | 
			
		||||
 | 
			
		||||
/// Error type for template rendering
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
pub struct TemplateError {
 | 
			
		||||
    pub message: String,
 | 
			
		||||
    pub details: String,
 | 
			
		||||
@@ -46,7 +47,7 @@ impl Function for NowFunction {
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let now = Utc::now();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Special case for just getting the year
 | 
			
		||||
        if args.get("year").and_then(|v| v.as_bool()).unwrap_or(false) {
 | 
			
		||||
            return Ok(Value::String(now.format("%Y").to_string()));
 | 
			
		||||
@@ -68,14 +69,10 @@ impl Function for FormatDateFunction {
 | 
			
		||||
                None => {
 | 
			
		||||
                    return Err(tera::Error::msg(
 | 
			
		||||
                        "The 'timestamp' argument must be a valid timestamp",
 | 
			
		||||
                    ))
 | 
			
		||||
                    ));
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            None => {
 | 
			
		||||
                return Err(tera::Error::msg(
 | 
			
		||||
                    "The 'timestamp' argument is required",
 | 
			
		||||
                ))
 | 
			
		||||
            }
 | 
			
		||||
            None => return Err(tera::Error::msg("The 'timestamp' argument is required")),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let format = match args.get("format") {
 | 
			
		||||
@@ -89,23 +86,21 @@ impl Function for FormatDateFunction {
 | 
			
		||||
        // Convert timestamp to DateTime using the non-deprecated method
 | 
			
		||||
        let datetime = match DateTime::from_timestamp(timestamp, 0) {
 | 
			
		||||
            Some(dt) => dt,
 | 
			
		||||
            None => {
 | 
			
		||||
                return Err(tera::Error::msg(
 | 
			
		||||
                    "Failed to convert timestamp to datetime",
 | 
			
		||||
                ))
 | 
			
		||||
            }
 | 
			
		||||
            None => return Err(tera::Error::msg("Failed to convert timestamp to datetime")),
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        Ok(Value::String(datetime.format(format).to_string()))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Formats a date for display
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
pub fn format_date(date: &DateTime<Utc>, format: &str) -> String {
 | 
			
		||||
    date.format(format).to_string()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Truncates a string to a maximum length and adds an ellipsis if truncated
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
pub fn truncate_string(s: &str, max_length: usize) -> String {
 | 
			
		||||
    if s.len() <= max_length {
 | 
			
		||||
        s.to_string()
 | 
			
		||||
@@ -124,38 +119,41 @@ pub fn render_template(
 | 
			
		||||
    ctx: &Context,
 | 
			
		||||
) -> Result<HttpResponse, Error> {
 | 
			
		||||
    println!("DEBUG: Attempting to render template: {}", template_name);
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Print all context keys for debugging
 | 
			
		||||
    let mut keys = Vec::new();
 | 
			
		||||
    for (key, _) in ctx.clone().into_json().as_object().unwrap().iter() {
 | 
			
		||||
        keys.push(key.clone());
 | 
			
		||||
    }
 | 
			
		||||
    println!("DEBUG: Context keys: {:?}", keys);
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    match tmpl.render(template_name, ctx) {
 | 
			
		||||
        Ok(content) => {
 | 
			
		||||
            println!("DEBUG: Successfully rendered template: {}", template_name);
 | 
			
		||||
            Ok(HttpResponse::Ok().content_type("text/html").body(content))
 | 
			
		||||
        },
 | 
			
		||||
        }
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            // Log the error with more details
 | 
			
		||||
            println!("DEBUG: Template rendering error for {}: {}", template_name, e);
 | 
			
		||||
            println!(
 | 
			
		||||
                "DEBUG: Template rendering error for {}: {}",
 | 
			
		||||
                template_name, e
 | 
			
		||||
            );
 | 
			
		||||
            println!("DEBUG: Error details: {:?}", e);
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            // Print the error cause chain for better debugging
 | 
			
		||||
            let mut current_error: Option<&dyn StdError> = Some(&e);
 | 
			
		||||
            let mut error_chain = Vec::new();
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            while let Some(error) = current_error {
 | 
			
		||||
                error_chain.push(format!("{}", error));
 | 
			
		||||
                current_error = error.source();
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            println!("DEBUG: Error chain: {:?}", error_chain);
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            // Log the error
 | 
			
		||||
            log::error!("Template rendering error: {}", e);
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            // Create a simple error response with more detailed information
 | 
			
		||||
            let error_html = format!(
 | 
			
		||||
                r#"<!DOCTYPE html>
 | 
			
		||||
@@ -187,9 +185,9 @@ pub fn render_template(
 | 
			
		||||
                e,
 | 
			
		||||
                error_chain.join("\n")
 | 
			
		||||
            );
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            println!("DEBUG: Returning simple error page");
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            Ok(HttpResponse::InternalServerError()
 | 
			
		||||
                .content_type("text/html")
 | 
			
		||||
                .body(error_html))
 | 
			
		||||
@@ -207,4 +205,4 @@ mod tests {
 | 
			
		||||
        assert_eq!(truncate_string("Hello, world!", 5), "Hello...");
 | 
			
		||||
        assert_eq!(truncate_string("", 5), "");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user