fix: Remove warnings

This commit is contained in:
Mahmoud Emad 2025-05-18 09:48:28 +03:00
parent e4e403e231
commit 60198dc2d4
20 changed files with 1411 additions and 764 deletions

View File

@ -1,6 +1,6 @@
use std::env;
use config::{Config, ConfigError, File}; use config::{Config, ConfigError, File};
use serde::Deserialize; use serde::Deserialize;
use std::env;
/// Application configuration /// Application configuration
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
@ -13,6 +13,7 @@ pub struct AppConfig {
/// Server configuration /// Server configuration
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
#[allow(dead_code)]
pub struct ServerConfig { pub struct ServerConfig {
/// Host address to bind to /// Host address to bind to
pub host: String, pub host: String,
@ -50,7 +51,8 @@ impl AppConfig {
} }
// Override with environment variables (e.g., SERVER__HOST, SERVER__PORT) // 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 // Build and deserialize the config
let config = config_builder.build()?; let config = config_builder.build()?;
@ -61,4 +63,4 @@ impl AppConfig {
/// Returns the application configuration /// Returns the application configuration
pub fn get_config() -> AppConfig { pub fn get_config() -> AppConfig {
AppConfig::new().expect("Failed to load configuration") AppConfig::new().expect("Failed to load configuration")
} }

File diff suppressed because it is too large Load Diff

View File

@ -25,6 +25,7 @@ lazy_static! {
/// Controller for handling authentication-related routes /// Controller for handling authentication-related routes
pub struct AuthController; pub struct AuthController;
#[allow(dead_code)]
impl AuthController { impl AuthController {
/// Generate a JWT token for a user /// Generate a JWT token for a user
fn generate_token(email: &str, role: &UserRole) -> Result<String, jsonwebtoken::errors::Error> { fn generate_token(email: &str, role: &UserRole) -> Result<String, jsonwebtoken::errors::Error> {

View File

@ -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 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 // Form structs for company operations
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub struct CompanyRegistrationForm { pub struct CompanyRegistrationForm {
pub company_name: String, pub company_name: String,
pub company_type: String, pub company_type: String,
@ -20,59 +20,69 @@ impl CompanyController {
// Display the company management dashboard // Display the company management dashboard
pub async fn index(tmpl: web::Data<Tera>, req: HttpRequest) -> Result<HttpResponse> { pub async fn index(tmpl: web::Data<Tera>, req: HttpRequest) -> Result<HttpResponse> {
let mut context = Context::new(); let mut context = Context::new();
println!("DEBUG: Starting Company dashboard rendering"); println!("DEBUG: Starting Company dashboard rendering");
// Add active_page for navigation highlighting // Add active_page for navigation highlighting
context.insert("active_page", &"company"); context.insert("active_page", &"company");
// Parse query parameters // Parse query parameters
let query_string = req.query_string(); let query_string = req.query_string();
// Check for success message // Check for success message
if let Some(pos) = query_string.find("success=") { if let Some(pos) = query_string.find("success=") {
let start = pos + 8; // length of "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 success = &query_string[start..end];
let decoded = urlencoding::decode(success).unwrap_or_else(|_| success.into()); let decoded = urlencoding::decode(success).unwrap_or_else(|_| success.into());
context.insert("success", &decoded); context.insert("success", &decoded);
} }
// Check for entity context // Check for entity context
if let Some(pos) = query_string.find("entity=") { if let Some(pos) = query_string.find("entity=") {
let start = pos + 7; // length of "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]; let entity = &query_string[start..end];
context.insert("entity", &entity); context.insert("entity", &entity);
// Also get entity name if present // Also get entity name if present
if let Some(pos) = query_string.find("entity_name=") { if let Some(pos) = query_string.find("entity_name=") {
let start = pos + 12; // length of "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 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); context.insert("entity_name", &decoded_name);
println!("DEBUG: Entity context set to {} ({})", entity, decoded_name); println!("DEBUG: Entity context set to {} ({})", entity, decoded_name);
} }
} }
println!("DEBUG: Rendering Company dashboard template"); println!("DEBUG: Rendering Company dashboard template");
let response = render_template(&tmpl, "company/index.html", &context); let response = render_template(&tmpl, "company/index.html", &context);
println!("DEBUG: Finished rendering Company dashboard template"); println!("DEBUG: Finished rendering Company dashboard template");
response response
} }
// View company details // 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 company_id = path.into_inner();
let mut context = Context::new(); let mut context = Context::new();
println!("DEBUG: Viewing company details for {}", company_id); println!("DEBUG: Viewing company details for {}", company_id);
// Add active_page for navigation highlighting // Add active_page for navigation highlighting
context.insert("active_page", &"company"); context.insert("active_page", &"company");
context.insert("company_id", &company_id); context.insert("company_id", &company_id);
// In a real application, we would fetch company data from a database // In a real application, we would fetch company data from a database
// For now, we'll use mock data based on the company_id // For now, we'll use mock data based on the company_id
match company_id.as_str() { match company_id.as_str() {
@ -85,14 +95,11 @@ impl CompanyController {
context.insert("plan", &"Startup FZC - $50/month"); context.insert("plan", &"Startup FZC - $50/month");
context.insert("next_billing", &"2025-06-01"); context.insert("next_billing", &"2025-06-01");
context.insert("payment_method", &"Credit Card (****4582)"); context.insert("payment_method", &"Credit Card (****4582)");
// Shareholders data // Shareholders data
let shareholders = vec![ let shareholders = vec![("John Smith", "60%"), ("Sarah Johnson", "40%")];
("John Smith", "60%"),
("Sarah Johnson", "40%"),
];
context.insert("shareholders", &shareholders); context.insert("shareholders", &shareholders);
// Contracts data // Contracts data
let contracts = vec![ let contracts = vec![
("Articles of Incorporation", "Signed"), ("Articles of Incorporation", "Signed"),
@ -100,7 +107,7 @@ impl CompanyController {
("Digital Asset Issuance", "Signed"), ("Digital Asset Issuance", "Signed"),
]; ];
context.insert("contracts", &contracts); context.insert("contracts", &contracts);
}, }
"company2" => { "company2" => {
context.insert("company_name", &"Blockchain Innovations Ltd"); context.insert("company_name", &"Blockchain Innovations Ltd");
context.insert("company_type", &"Growth FZC"); context.insert("company_type", &"Growth FZC");
@ -110,7 +117,7 @@ impl CompanyController {
context.insert("plan", &"Growth FZC - $100/month"); context.insert("plan", &"Growth FZC - $100/month");
context.insert("next_billing", &"2025-06-15"); context.insert("next_billing", &"2025-06-15");
context.insert("payment_method", &"Bank Transfer"); context.insert("payment_method", &"Bank Transfer");
// Shareholders data // Shareholders data
let shareholders = vec![ let shareholders = vec![
("Michael Chen", "35%"), ("Michael Chen", "35%"),
@ -118,7 +125,7 @@ impl CompanyController {
("David Okonkwo", "30%"), ("David Okonkwo", "30%"),
]; ];
context.insert("shareholders", &shareholders); context.insert("shareholders", &shareholders);
// Contracts data // Contracts data
let contracts = vec![ let contracts = vec![
("Articles of Incorporation", "Signed"), ("Articles of Incorporation", "Signed"),
@ -127,7 +134,7 @@ impl CompanyController {
("Physical Asset Holding", "Signed"), ("Physical Asset Holding", "Signed"),
]; ];
context.insert("contracts", &contracts); context.insert("contracts", &contracts);
}, }
"company3" => { "company3" => {
context.insert("company_name", &"Sustainable Energy Cooperative"); context.insert("company_name", &"Sustainable Energy Cooperative");
context.insert("company_type", &"Cooperative FZC"); context.insert("company_type", &"Cooperative FZC");
@ -137,7 +144,7 @@ impl CompanyController {
context.insert("plan", &"Cooperative FZC - $200/month"); context.insert("plan", &"Cooperative FZC - $200/month");
context.insert("next_billing", &"Pending Activation"); context.insert("next_billing", &"Pending Activation");
context.insert("payment_method", &"Pending"); context.insert("payment_method", &"Pending");
// Shareholders data // Shareholders data
let shareholders = vec![ let shareholders = vec![
("Community Energy Group", "40%"), ("Community Energy Group", "40%"),
@ -145,7 +152,7 @@ impl CompanyController {
("Sustainable Living Collective", "30%"), ("Sustainable Living Collective", "30%"),
]; ];
context.insert("shareholders", &shareholders); context.insert("shareholders", &shareholders);
// Contracts data // Contracts data
let contracts = vec![ let contracts = vec![
("Articles of Incorporation", "Signed"), ("Articles of Incorporation", "Signed"),
@ -153,7 +160,7 @@ impl CompanyController {
("Cooperative Governance", "Pending"), ("Cooperative Governance", "Pending"),
]; ];
context.insert("contracts", &contracts); context.insert("contracts", &contracts);
}, }
_ => { _ => {
// If company_id is not recognized, redirect to company index // If company_id is not recognized, redirect to company index
return Ok(HttpResponse::Found() return Ok(HttpResponse::Found()
@ -161,51 +168,56 @@ impl CompanyController {
.finish()); .finish());
} }
} }
println!("DEBUG: Rendering company view template"); println!("DEBUG: Rendering company view template");
let response = render_template(&tmpl, "company/view.html", &context); let response = render_template(&tmpl, "company/view.html", &context);
println!("DEBUG: Finished rendering company view template"); println!("DEBUG: Finished rendering company view template");
response response
} }
// Switch to entity context // Switch to entity context
pub async fn switch_entity(path: web::Path<String>) -> Result<HttpResponse> { pub async fn switch_entity(path: web::Path<String>) -> Result<HttpResponse> {
let company_id = path.into_inner(); let company_id = path.into_inner();
println!("DEBUG: Switching to entity context for {}", company_id); 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) // Get company name based on ID (in a real app, this would come from a database)
let company_name = match company_id.as_str() { let company_name = match company_id.as_str() {
"company1" => "Zanzibar Digital Solutions", "company1" => "Zanzibar Digital Solutions",
"company2" => "Blockchain Innovations Ltd", "company2" => "Blockchain Innovations Ltd",
"company3" => "Sustainable Energy Cooperative", "company3" => "Sustainable Energy Cooperative",
_ => "Unknown Company" _ => "Unknown Company",
}; };
// In a real application, we would set a session/cookie for the current entity // 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 // 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 success_message = format!("Switched to {} entity context", company_name);
let encoded_message = urlencoding::encode(&success_message); let encoded_message = urlencoding::encode(&success_message);
Ok(HttpResponse::Found() Ok(HttpResponse::Found()
.append_header(("Location", format!("/company?success={}&entity={}&entity_name={}", .append_header((
encoded_message, company_id, urlencoding::encode(company_name)))) "Location",
format!(
"/company?success={}&entity={}&entity_name={}",
encoded_message,
company_id,
urlencoding::encode(company_name)
),
))
.finish()) .finish())
} }
// Process company registration // Process company registration
pub async fn register( pub async fn register(mut form: actix_multipart::Multipart) -> Result<HttpResponse> {
mut form: actix_multipart::Multipart, use actix_web::http::header;
) -> Result<HttpResponse> {
use actix_web::{http::header};
use futures_util::stream::StreamExt as _; use futures_util::stream::StreamExt as _;
use std::collections::HashMap; use std::collections::HashMap;
println!("DEBUG: Processing company registration request"); println!("DEBUG: Processing company registration request");
let mut fields: HashMap<String, String> = HashMap::new(); let mut fields: HashMap<String, String> = HashMap::new();
let mut files = Vec::new(); let mut files = Vec::new();
// Parse multipart form // Parse multipart form
while let Some(Ok(mut field)) = form.next().await { while let Some(Ok(mut field)) = form.next().await {
let mut value = Vec::new(); let mut value = Vec::new();
@ -213,33 +225,47 @@ impl CompanyController {
let data = chunk.unwrap(); let data = chunk.unwrap();
value.extend_from_slice(&data); value.extend_from_slice(&data);
} }
// Get field name from content disposition // Get field name from content disposition
let cd = field.content_disposition(); let cd = field.content_disposition();
if let Some(name) = cd.get_name() { if let Some(name) = cd.get_name() {
if name == "company_docs" { if name == "company_docs" {
files.push(value); // Just collect files in memory for now files.push(value); // Just collect files in memory for now
} else { } 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 // Extract company details
let company_name = fields.get("company_name").cloned().unwrap_or_default(); let company_name = fields.get("company_name").cloned().unwrap_or_default();
let company_type = fields.get("company_type").cloned().unwrap_or_default(); let company_type = fields.get("company_type").cloned().unwrap_or_default();
let shareholders = fields.get("shareholders").cloned().unwrap_or_default(); let shareholders = fields.get("shareholders").cloned().unwrap_or_default();
// Log received fields (mock DB insert) // Log received fields (mock DB insert)
println!("[Company Registration] Name: {}, Type: {}, Shareholders: {}, Files: {}", println!(
company_name, company_type, shareholders, files.len()); "[Company Registration] Name: {}, Type: {}, Shareholders: {}, Files: {}",
company_name,
company_type,
shareholders,
files.len()
);
// Create success message // 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 // Redirect back to /company with success message
Ok(HttpResponse::SeeOther() 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()) .finish())
} }
} }

View File

@ -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::web::Query;
use actix_web::{Error, HttpResponse, Result, web};
use chrono::{Duration, Utc};
use serde::Deserialize;
use std::collections::HashMap; 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; use crate::utils::render_template;
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub struct ContractForm { pub struct ContractForm {
pub title: String, pub title: String,
pub description: String, pub description: String,
@ -18,6 +21,7 @@ pub struct ContractForm {
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub struct SignerForm { pub struct SignerForm {
pub name: String, pub name: String,
pub email: String, pub email: String,
@ -29,98 +33,99 @@ impl ContractController {
// Display the contracts dashboard // Display the contracts dashboard
pub async fn index(tmpl: web::Data<Tera>) -> Result<HttpResponse, Error> { pub async fn index(tmpl: web::Data<Tera>) -> Result<HttpResponse, Error> {
let mut context = Context::new(); let mut context = Context::new();
let contracts = Self::get_mock_contracts(); let contracts = Self::get_mock_contracts();
let stats = ContractStatistics::new(&contracts); let stats = ContractStatistics::new(&contracts);
// Add active_page for navigation highlighting // Add active_page for navigation highlighting
context.insert("active_page", &"contracts"); context.insert("active_page", &"contracts");
// Add stats // Add stats
context.insert("stats", &serde_json::to_value(stats).unwrap()); context.insert("stats", &serde_json::to_value(stats).unwrap());
// Add recent contracts // Add recent contracts
let recent_contracts: Vec<serde_json::Map<String, serde_json::Value>> = contracts let recent_contracts: Vec<serde_json::Map<String, serde_json::Value>> = contracts
.iter() .iter()
.take(5) .take(5)
.map(|c| Self::contract_to_json(c)) .map(|c| Self::contract_to_json(c))
.collect(); .collect();
context.insert("recent_contracts", &recent_contracts); context.insert("recent_contracts", &recent_contracts);
// Add pending signature contracts // Add pending signature contracts
let pending_signature_contracts: Vec<serde_json::Map<String, serde_json::Value>> = contracts let pending_signature_contracts: Vec<serde_json::Map<String, serde_json::Value>> =
.iter() contracts
.filter(|c| c.status == ContractStatus::PendingSignatures) .iter()
.map(|c| Self::contract_to_json(c)) .filter(|c| c.status == ContractStatus::PendingSignatures)
.collect(); .map(|c| Self::contract_to_json(c))
.collect();
context.insert("pending_signature_contracts", &pending_signature_contracts); context.insert("pending_signature_contracts", &pending_signature_contracts);
// Add draft contracts // Add draft contracts
let draft_contracts: Vec<serde_json::Map<String, serde_json::Value>> = contracts let draft_contracts: Vec<serde_json::Map<String, serde_json::Value>> = contracts
.iter() .iter()
.filter(|c| c.status == ContractStatus::Draft) .filter(|c| c.status == ContractStatus::Draft)
.map(|c| Self::contract_to_json(c)) .map(|c| Self::contract_to_json(c))
.collect(); .collect();
context.insert("draft_contracts", &draft_contracts); context.insert("draft_contracts", &draft_contracts);
render_template(&tmpl, "contracts/index.html", &context) render_template(&tmpl, "contracts/index.html", &context)
} }
// Display the list of all contracts // Display the list of all contracts
pub async fn list(tmpl: web::Data<Tera>) -> Result<HttpResponse, Error> { pub async fn list(tmpl: web::Data<Tera>) -> Result<HttpResponse, Error> {
let mut context = Context::new(); let mut context = Context::new();
let contracts = Self::get_mock_contracts(); let contracts = Self::get_mock_contracts();
let contracts_data: Vec<serde_json::Map<String, serde_json::Value>> = contracts let contracts_data: Vec<serde_json::Map<String, serde_json::Value>> = contracts
.iter() .iter()
.map(|c| Self::contract_to_json(c)) .map(|c| Self::contract_to_json(c))
.collect(); .collect();
// Add active_page for navigation highlighting // Add active_page for navigation highlighting
context.insert("active_page", &"contracts"); context.insert("active_page", &"contracts");
context.insert("contracts", &contracts_data); context.insert("contracts", &contracts_data);
context.insert("filter", &"all"); context.insert("filter", &"all");
render_template(&tmpl, "contracts/contracts.html", &context) render_template(&tmpl, "contracts/contracts.html", &context)
} }
// Display the list of user's contracts // Display the list of user's contracts
pub async fn my_contracts(tmpl: web::Data<Tera>) -> Result<HttpResponse, Error> { pub async fn my_contracts(tmpl: web::Data<Tera>) -> Result<HttpResponse, Error> {
let mut context = Context::new(); let mut context = Context::new();
let contracts = Self::get_mock_contracts(); let contracts = Self::get_mock_contracts();
let contracts_data: Vec<serde_json::Map<String, serde_json::Value>> = contracts let contracts_data: Vec<serde_json::Map<String, serde_json::Value>> = contracts
.iter() .iter()
.map(|c| Self::contract_to_json(c)) .map(|c| Self::contract_to_json(c))
.collect(); .collect();
// Add active_page for navigation highlighting // Add active_page for navigation highlighting
context.insert("active_page", &"contracts"); context.insert("active_page", &"contracts");
context.insert("contracts", &contracts_data); context.insert("contracts", &contracts_data);
render_template(&tmpl, "contracts/my_contracts.html", &context) render_template(&tmpl, "contracts/my_contracts.html", &context)
} }
// Display a specific contract // Display a specific contract
pub async fn detail( pub async fn detail(
tmpl: web::Data<Tera>, tmpl: web::Data<Tera>,
path: web::Path<String>, path: web::Path<String>,
query: Query<HashMap<String, String>> query: Query<HashMap<String, String>>,
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, Error> {
let contract_id = path.into_inner(); let contract_id = path.into_inner();
let mut context = Context::new(); let mut context = Context::new();
// Add active_page for navigation highlighting // Add active_page for navigation highlighting
context.insert("active_page", &"contracts"); context.insert("active_page", &"contracts");
// Find the contract by ID // Find the contract by ID
let contracts = Self::get_mock_contracts(); let contracts = Self::get_mock_contracts();
// For demo purposes, if the ID doesn't match exactly, just show the first contract // 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 // 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) { 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 // For demo, just use the first contract
contracts.first().unwrap() contracts.first().unwrap()
}; };
// Convert contract to JSON // Convert contract to JSON
let contract_json = Self::contract_to_json(contract); let contract_json = Self::contract_to_json(contract);
@ -137,10 +142,13 @@ impl ContractController {
context.insert("contract", &contract_json); context.insert("contract", &contract_json);
// If this contract uses multi-page markdown, load the selected section // 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) { if let (Some(content_dir), Some(toc)) = (&contract.content_dir, &contract.toc) {
use pulldown_cmark::{Options, Parser, html};
use std::fs; use std::fs;
use pulldown_cmark::{Parser, Options, html};
// Helper to flatten toc recursively // Helper to flatten toc recursively
fn flatten_toc<'a>(items: &'a Vec<TocItem>, out: &mut Vec<&'a TocItem>) { fn flatten_toc<'a>(items: &'a Vec<TocItem>, out: &mut Vec<&'a TocItem>) {
for item in items { for item in items {
@ -154,15 +162,28 @@ impl ContractController {
flatten_toc(&toc, &mut flat_toc); flatten_toc(&toc, &mut flat_toc);
let section_param = query.get("section"); let section_param = query.get("section");
let selected_file = section_param let selected_file = section_param
.and_then(|f| flat_toc.iter().find(|item| item.file == *f).map(|item| item.file.clone())) .and_then(|f| {
.unwrap_or_else(|| flat_toc.get(0).map(|item| item.file.clone()).unwrap_or_default()); 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); context.insert("section", &selected_file);
let rel_path = format!("{}/{}", content_dir, selected_file); let rel_path = format!("{}/{}", content_dir, selected_file);
let abs_path = match std::env::current_dir() { let abs_path = match std::env::current_dir() {
Ok(dir) => dir.join(&rel_path), Ok(dir) => dir.join(&rel_path),
Err(_) => std::path::PathBuf::from(&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) { match fs::read_to_string(&abs_path) {
Ok(md) => { Ok(md) => {
println!("DEBUG: Successfully read markdown file"); println!("DEBUG: Successfully read markdown file");
@ -170,52 +191,63 @@ impl ContractController {
let mut html_output = String::new(); let mut html_output = String::new();
html::push_html(&mut html_output, parser); html::push_html(&mut html_output, parser);
context.insert("contract_section_content", &html_output); context.insert("contract_section_content", &html_output);
}, }
Err(e) => { 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); println!("{}", error_msg);
context.insert("contract_section_content_error", &error_msg); context.insert("contract_section_content_error", &error_msg);
} }
} }
context.insert("toc", &toc); context.insert("toc", &toc);
} }
// Count signed signers for the template // 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); context.insert("signed_signers", &signed_signers);
// Count pending signers for the template // 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); context.insert("pending_signers", &pending_signers);
// For demo purposes, set user_has_signed to false // For demo purposes, set user_has_signed to false
// In a real app, we would check if the current user has already signed // In a real app, we would check if the current user has already signed
context.insert("user_has_signed", &false); context.insert("user_has_signed", &false);
render_template(&tmpl, "contracts/contract_detail.html", &context) render_template(&tmpl, "contracts/contract_detail.html", &context)
} }
// Display the create contract form // Display the create contract form
pub async fn create_form(tmpl: web::Data<Tera>) -> Result<HttpResponse, Error> { pub async fn create_form(tmpl: web::Data<Tera>) -> Result<HttpResponse, Error> {
let mut context = Context::new(); let mut context = Context::new();
// Add active_page for navigation highlighting // Add active_page for navigation highlighting
context.insert("active_page", &"contracts"); context.insert("active_page", &"contracts");
// Add contract types for dropdown // Add contract types for dropdown
let contract_types = vec![ let contract_types = vec![
("Service", "Service Agreement"), ("Service", "Service Agreement"),
("Employment", "Employment Contract"), ("Employment", "Employment Contract"),
("NDA", "Non-Disclosure Agreement"), ("NDA", "Non-Disclosure Agreement"),
("SLA", "Service Level Agreement"), ("SLA", "Service Level Agreement"),
("Other", "Other") ("Other", "Other"),
]; ];
context.insert("contract_types", &contract_types); context.insert("contract_types", &contract_types);
render_template(&tmpl, "contracts/create_contract.html", &context) render_template(&tmpl, "contracts/create_contract.html", &context)
} }
// Process the create contract form // Process the create contract form
pub async fn create( pub async fn create(
_tmpl: web::Data<Tera>, _tmpl: web::Data<Tera>,
@ -223,158 +255,334 @@ impl ContractController {
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, Error> {
// In a real application, we would save the contract to the database // In a real application, we would save the contract to the database
// For now, we'll just redirect to the contracts list // 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 // Helper method to convert Contract to a JSON object for templates
fn contract_to_json(contract: &Contract) -> serde_json::Map<String, serde_json::Value> { fn contract_to_json(contract: &Contract) -> serde_json::Map<String, serde_json::Value> {
let mut map = serde_json::Map::new(); let mut map = serde_json::Map::new();
// Basic contract info // Basic contract info
map.insert("id".to_string(), serde_json::Value::String(contract.id.clone())); map.insert(
map.insert("title".to_string(), serde_json::Value::String(contract.title.clone())); "id".to_string(),
map.insert("description".to_string(), serde_json::Value::String(contract.description.clone())); serde_json::Value::String(contract.id.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(
map.insert("created_by".to_string(), serde_json::Value::String(contract.created_by.clone())); "title".to_string(),
map.insert("created_at".to_string(), serde_json::Value::String(contract.created_at.format("%Y-%m-%d").to_string())); serde_json::Value::String(contract.title.clone()),
map.insert("updated_at".to_string(), serde_json::Value::String(contract.updated_at.format("%Y-%m-%d").to_string())); );
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 // Organization info
if let Some(org) = &contract.organization_id { 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 { } else {
map.insert("organization".to_string(), serde_json::Value::Null); map.insert("organization".to_string(), serde_json::Value::Null);
} }
// Add signers // Add signers
let signers: Vec<serde_json::Value> = contract.signers.iter() let signers: Vec<serde_json::Value> = contract
.signers
.iter()
.map(|s| { .map(|s| {
let mut signer_map = serde_json::Map::new(); let mut signer_map = serde_json::Map::new();
signer_map.insert("id".to_string(), serde_json::Value::String(s.id.clone())); 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(
signer_map.insert("email".to_string(), serde_json::Value::String(s.email.clone())); "name".to_string(),
signer_map.insert("status".to_string(), serde_json::Value::String(s.status.as_str().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 { 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 { } else {
// For display purposes, add a placeholder date for pending signers // For display purposes, add a placeholder date for pending signers
if s.status == SignerStatus::Pending { 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 { } 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 { 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 { } 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) serde_json::Value::Object(signer_map)
}) })
.collect(); .collect();
map.insert("signers".to_string(), serde_json::Value::Array(signers)); map.insert("signers".to_string(), serde_json::Value::Array(signers));
// Add pending_signers count for templates // Add pending_signers count for templates
let pending_signers = contract.signers.iter().filter(|s| s.status == SignerStatus::Pending).count(); let pending_signers = contract
map.insert("pending_signers".to_string(), serde_json::Value::Number(serde_json::Number::from(pending_signers))); .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 // Add signed_signers count for templates
let signed_signers = contract.signers.iter().filter(|s| s.status == SignerStatus::Signed).count(); let signed_signers = contract
map.insert("signed_signers".to_string(), serde_json::Value::Number(serde_json::Number::from(signed_signers))); .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 // Add revisions
let revisions: Vec<serde_json::Value> = contract.revisions.iter() let revisions: Vec<serde_json::Value> = contract
.revisions
.iter()
.map(|r| { .map(|r| {
let mut revision_map = serde_json::Map::new(); 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(
revision_map.insert("content".to_string(), serde_json::Value::String(r.content.clone())); "version".to_string(),
revision_map.insert("created_at".to_string(), serde_json::Value::String(r.created_at.format("%Y-%m-%d").to_string())); serde_json::Value::Number(serde_json::Number::from(r.version)),
revision_map.insert("created_by".to_string(), serde_json::Value::String(r.created_by.clone())); );
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 { 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 // 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 { } else {
revision_map.insert("comments".to_string(), serde_json::Value::String("".to_string())); revision_map.insert(
revision_map.insert("notes".to_string(), serde_json::Value::String("".to_string())); "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) serde_json::Value::Object(revision_map)
}) })
.collect(); .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 // 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 // Add latest_revision as an object
if !contract.revisions.is_empty() { if !contract.revisions.is_empty() {
// Find the latest revision based on version number // Find the latest revision based on version number
if let Some(latest) = contract.revisions.iter().max_by_key(|r| r.version) { if let Some(latest) = contract.revisions.iter().max_by_key(|r| r.version) {
let mut latest_revision_map = serde_json::Map::new(); 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(
latest_revision_map.insert("content".to_string(), serde_json::Value::String(latest.content.clone())); "version".to_string(),
latest_revision_map.insert("created_at".to_string(), serde_json::Value::String(latest.created_at.format("%Y-%m-%d").to_string())); serde_json::Value::Number(serde_json::Number::from(latest.version)),
latest_revision_map.insert("created_by".to_string(), serde_json::Value::String(latest.created_by.clone())); );
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 { if let Some(comments) = &latest.comments {
latest_revision_map.insert("comments".to_string(), serde_json::Value::String(comments.clone())); latest_revision_map.insert(
latest_revision_map.insert("notes".to_string(), serde_json::Value::String(comments.clone())); "comments".to_string(),
serde_json::Value::String(comments.clone()),
);
latest_revision_map.insert(
"notes".to_string(),
serde_json::Value::String(comments.clone()),
);
} else { } else {
latest_revision_map.insert("comments".to_string(), serde_json::Value::String("".to_string())); latest_revision_map.insert(
latest_revision_map.insert("notes".to_string(), serde_json::Value::String("".to_string())); "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 { } else {
// Create an empty latest_revision object to avoid template errors // Create an empty latest_revision object to avoid template errors
let mut empty_revision = serde_json::Map::new(); 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(
empty_revision.insert("content".to_string(), serde_json::Value::String("No content available".to_string())); "version".to_string(),
empty_revision.insert("created_at".to_string(), serde_json::Value::String("N/A".to_string())); serde_json::Value::Number(serde_json::Number::from(0)),
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(
empty_revision.insert("notes".to_string(), serde_json::Value::String("".to_string())); "content".to_string(),
serde_json::Value::String("No content available".to_string()),
map.insert("latest_revision".to_string(), serde_json::Value::Object(empty_revision)); );
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 { } else {
// Create an empty latest_revision object to avoid template errors // Create an empty latest_revision object to avoid template errors
let mut empty_revision = serde_json::Map::new(); 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(
empty_revision.insert("content".to_string(), serde_json::Value::String("No content available".to_string())); "version".to_string(),
empty_revision.insert("created_at".to_string(), serde_json::Value::String("N/A".to_string())); serde_json::Value::Number(serde_json::Number::from(0)),
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(
empty_revision.insert("notes".to_string(), serde_json::Value::String("".to_string())); "content".to_string(),
serde_json::Value::String("No content available".to_string()),
map.insert("latest_revision".to_string(), serde_json::Value::Object(empty_revision)); );
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 // Add effective and expiration dates if present
if let Some(effective_date) = &contract.effective_date { 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 { 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 map
} }
// Generate mock contracts for testing // Generate mock contracts for testing
fn get_mock_contracts() -> Vec<Contract> { fn get_mock_contracts() -> Vec<Contract> {
let mut contracts = Vec::new(); let mut contracts = Vec::new();
// Mock contract 1 - Signed Service Agreement // Mock contract 1 - Signed Service Agreement
let mut contract1 = Contract { let mut contract1 = Contract {
content_dir: None, content_dir: None,
@ -394,7 +602,7 @@ impl ContractController {
revisions: Vec::new(), revisions: Vec::new(),
current_version: 2, current_version: 2,
}; };
// Add signers to contract 1 // Add signers to contract 1
contract1.signers.push(ContractSigner { contract1.signers.push(ContractSigner {
id: "signer-001".to_string(), id: "signer-001".to_string(),
@ -404,7 +612,7 @@ impl ContractController {
signed_at: Some(Utc::now() - Duration::days(5)), signed_at: Some(Utc::now() - Duration::days(5)),
comments: Some("Approved as per our discussion.".to_string()), comments: Some("Approved as per our discussion.".to_string()),
}); });
contract1.signers.push(ContractSigner { contract1.signers.push(ContractSigner {
id: "signer-002".to_string(), id: "signer-002".to_string(),
name: "Nala Okafor".to_string(), name: "Nala Okafor".to_string(),
@ -413,7 +621,7 @@ impl ContractController {
signed_at: Some(Utc::now() - Duration::days(6)), signed_at: Some(Utc::now() - Duration::days(6)),
comments: Some("Terms look good. Happy to proceed.".to_string()), comments: Some("Terms look good. Happy to proceed.".to_string()),
}); });
// Add revisions to contract 1 // Add revisions to contract 1
contract1.revisions.push(ContractRevision { contract1.revisions.push(ContractRevision {
version: 1, version: 1,
@ -422,7 +630,7 @@ impl ContractController {
created_by: "Wei Chen".to_string(), created_by: "Wei Chen".to_string(),
comments: Some("Initial draft of the service agreement.".to_string()), comments: Some("Initial draft of the service agreement.".to_string()),
}); });
contract1.revisions.push(ContractRevision { contract1.revisions.push(ContractRevision {
version: 2, 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(), 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(), created_by: "Wei Chen".to_string(),
comments: Some("Added data protection clause as requested by legal.".to_string()), comments: Some("Added data protection clause as requested by legal.".to_string()),
}); });
// Mock contract 2 - Pending Signatures // Mock contract 2 - Pending Signatures
let mut contract2 = Contract { let mut contract2 = Contract {
content_dir: None, content_dir: None,
@ -450,7 +658,7 @@ impl ContractController {
revisions: Vec::new(), revisions: Vec::new(),
current_version: 1, current_version: 1,
}; };
// Add signers to contract 2 // Add signers to contract 2
contract2.signers.push(ContractSigner { contract2.signers.push(ContractSigner {
id: "signer-003".to_string(), id: "signer-003".to_string(),
@ -460,7 +668,7 @@ impl ContractController {
signed_at: Some(Utc::now() - Duration::days(2)), signed_at: Some(Utc::now() - Duration::days(2)),
comments: None, comments: None,
}); });
contract2.signers.push(ContractSigner { contract2.signers.push(ContractSigner {
id: "signer-004".to_string(), id: "signer-004".to_string(),
name: "Maya Rodriguez".to_string(), name: "Maya Rodriguez".to_string(),
@ -469,7 +677,7 @@ impl ContractController {
signed_at: None, signed_at: None,
comments: None, comments: None,
}); });
contract2.signers.push(ContractSigner { contract2.signers.push(ContractSigner {
id: "signer-005".to_string(), id: "signer-005".to_string(),
name: "Jamal Washington".to_string(), name: "Jamal Washington".to_string(),
@ -478,7 +686,7 @@ impl ContractController {
signed_at: None, signed_at: None,
comments: None, comments: None,
}); });
// Add revisions to contract 2 // Add revisions to contract 2
contract2.revisions.push(ContractRevision { contract2.revisions.push(ContractRevision {
version: 1, version: 1,
@ -487,7 +695,7 @@ impl ContractController {
created_by: "Dr. Raj Patel".to_string(), created_by: "Dr. Raj Patel".to_string(),
comments: Some("Initial draft of the development agreement.".to_string()), comments: Some("Initial draft of the development agreement.".to_string()),
}); });
// Mock contract 3 - Draft // Mock contract 3 - Draft
let mut contract3 = Contract { let mut contract3 = Contract {
id: "contract-003".to_string(), id: "contract-003".to_string(),
@ -554,7 +762,6 @@ impl ContractController {
]), ]),
}; };
// Add potential signers to contract 3 (still in draft) // Add potential signers to contract 3 (still in draft)
contract3.signers.push(ContractSigner { contract3.signers.push(ContractSigner {
id: "signer-006".to_string(), id: "signer-006".to_string(),
@ -564,7 +771,7 @@ impl ContractController {
signed_at: None, signed_at: None,
comments: None, comments: None,
}); });
contract3.signers.push(ContractSigner { contract3.signers.push(ContractSigner {
id: "signer-007".to_string(), id: "signer-007".to_string(),
name: "Ibrahim Al-Farsi".to_string(), name: "Ibrahim Al-Farsi".to_string(),
@ -573,59 +780,57 @@ impl ContractController {
signed_at: None, signed_at: None,
comments: None, comments: None,
}); });
// Add ToC and content directory to contract 3 // Add ToC and content directory to contract 3
contract3.content_dir = Some("src/content/contract-003".to_string()); contract3.content_dir = Some("src/content/contract-003".to_string());
contract3.toc = Some(vec![ contract3.toc = Some(vec![TocItem {
TocItem { title: "Digital Asset Tokenization Agreement".to_string(),
title: "Digital Asset Tokenization Agreement".to_string(), file: "cover.md".to_string(),
file: "cover.md".to_string(), children: vec![
children: vec![ TocItem {
TocItem { title: "1. Purpose".to_string(),
title: "1. Purpose".to_string(), file: "1-purpose.md".to_string(),
file: "1-purpose.md".to_string(), children: vec![],
children: vec![], },
}, TocItem {
TocItem { title: "2. Tokenization Process".to_string(),
title: "2. Tokenization Process".to_string(), file: "2-tokenization-process.md".to_string(),
file: "2-tokenization-process.md".to_string(), children: vec![],
children: vec![], },
}, TocItem {
TocItem { title: "3. Revenue Sharing".to_string(),
title: "3. Revenue Sharing".to_string(), file: "3-revenue-sharing.md".to_string(),
file: "3-revenue-sharing.md".to_string(), children: vec![],
children: vec![], },
}, TocItem {
TocItem { title: "4. Governance".to_string(),
title: "4. Governance".to_string(), file: "4-governance.md".to_string(),
file: "4-governance.md".to_string(), children: vec![],
children: vec![], },
}, TocItem {
TocItem { title: "Appendix A: Properties".to_string(),
title: "Appendix A: Properties".to_string(), file: "appendix-a.md".to_string(),
file: "appendix-a.md".to_string(), children: vec![],
children: vec![], },
}, TocItem {
TocItem { title: "Appendix B: Specifications".to_string(),
title: "Appendix B: Specifications".to_string(), file: "appendix-b.md".to_string(),
file: "appendix-b.md".to_string(), children: vec![],
children: vec![], },
}, TocItem {
TocItem { title: "Appendix C: Revenue Formula".to_string(),
title: "Appendix C: Revenue Formula".to_string(), file: "appendix-c.md".to_string(),
file: "appendix-c.md".to_string(), children: vec![],
children: vec![], },
}, TocItem {
TocItem { title: "Appendix D: Governance Framework".to_string(),
title: "Appendix D: Governance Framework".to_string(), file: "appendix-d.md".to_string(),
file: "appendix-d.md".to_string(), children: vec![],
children: vec![], },
}, ],
], }]);
}
]);
// No revision content for contract 3, content is in markdown files. // No revision content for contract 3, content is in markdown files.
// Mock contract 4 - Rejected // Mock contract 4 - Rejected
let mut contract4 = Contract { let mut contract4 = Contract {
content_dir: None, content_dir: None,
@ -645,7 +850,7 @@ impl ContractController {
revisions: Vec::new(), revisions: Vec::new(),
current_version: 1, current_version: 1,
}; };
// Add signers to contract 4 with a rejection // Add signers to contract 4 with a rejection
contract4.signers.push(ContractSigner { contract4.signers.push(ContractSigner {
id: "signer-008".to_string(), id: "signer-008".to_string(),
@ -655,7 +860,7 @@ impl ContractController {
signed_at: Some(Utc::now() - Duration::days(10)), signed_at: Some(Utc::now() - Duration::days(10)),
comments: None, comments: None,
}); });
contract4.signers.push(ContractSigner { contract4.signers.push(ContractSigner {
id: "signer-009".to_string(), id: "signer-009".to_string(),
name: "Dr. Amina Diallo".to_string(), name: "Dr. Amina Diallo".to_string(),
@ -664,7 +869,7 @@ impl ContractController {
signed_at: Some(Utc::now() - Duration::days(8)), 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()), 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 // Add revisions to contract 4
contract4.revisions.push(ContractRevision { contract4.revisions.push(ContractRevision {
version: 1, version: 1,
@ -673,7 +878,7 @@ impl ContractController {
created_by: "Wei Chen".to_string(), created_by: "Wei Chen".to_string(),
comments: Some("Initial draft of the data sharing agreement.".to_string()), comments: Some("Initial draft of the data sharing agreement.".to_string()),
}); });
// Mock contract 5 - Active // Mock contract 5 - Active
let mut contract5 = Contract { let mut contract5 = Contract {
content_dir: None, content_dir: None,
@ -693,7 +898,7 @@ impl ContractController {
revisions: Vec::new(), revisions: Vec::new(),
current_version: 2, current_version: 2,
}; };
// Add signers to contract 5 // Add signers to contract 5
contract5.signers.push(ContractSigner { contract5.signers.push(ContractSigner {
id: "signer-010".to_string(), id: "signer-010".to_string(),
@ -703,7 +908,7 @@ impl ContractController {
signed_at: Some(Utc::now() - Duration::days(47)), signed_at: Some(Utc::now() - Duration::days(47)),
comments: None, comments: None,
}); });
contract5.signers.push(ContractSigner { contract5.signers.push(ContractSigner {
id: "signer-011".to_string(), id: "signer-011".to_string(),
name: "Li Wei".to_string(), name: "Li Wei".to_string(),
@ -712,7 +917,7 @@ impl ContractController {
signed_at: Some(Utc::now() - Duration::days(45)), signed_at: Some(Utc::now() - Duration::days(45)),
comments: Some("Approved after legal review.".to_string()), comments: Some("Approved after legal review.".to_string()),
}); });
// Add revisions to contract 5 // Add revisions to contract 5
contract5.revisions.push(ContractRevision { contract5.revisions.push(ContractRevision {
version: 1, version: 1,
@ -721,7 +926,7 @@ impl ContractController {
created_by: "Maya Rodriguez".to_string(), created_by: "Maya Rodriguez".to_string(),
comments: Some("Initial draft of the identity verification service agreement.".to_string()), comments: Some("Initial draft of the identity verification service agreement.".to_string()),
}); });
contract5.revisions.push(ContractRevision { contract5.revisions.push(ContractRevision {
version: 2, 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(), 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(), created_by: "Maya Rodriguez".to_string(),
comments: Some("Added compliance clause as requested by legal.".to_string()), comments: Some("Added compliance clause as requested by legal.".to_string()),
}); });
// Add all contracts to the vector // Add all contracts to the vector
contracts.push(contract1); contracts.push(contract1);
contracts.push(contract2); contracts.push(contract2);
contracts.push(contract3); contracts.push(contract3);
contracts.push(contract4); contracts.push(contract4);
contracts.push(contract5); contracts.push(contract5);
contracts contracts
} }
} }

View File

@ -1,12 +1,15 @@
use actix_web::{web, HttpResponse, Result};
use actix_web::HttpRequest; use actix_web::HttpRequest;
use tera::{Context, Tera}; use actix_web::{HttpResponse, Result, web};
use chrono::{Utc, Duration}; use chrono::{Duration, Utc};
use serde::Deserialize; use serde::Deserialize;
use tera::{Context, Tera};
use uuid::Uuid; use uuid::Uuid;
use crate::models::asset::{Asset, AssetType, AssetStatus}; use crate::models::asset::Asset;
use crate::models::defi::{DefiPosition, DefiPositionType, DefiPositionStatus, ProvidingPosition, ReceivingPosition, DEFI_DB}; use crate::models::defi::{
DEFI_DB, DefiPosition, DefiPositionStatus, DefiPositionType, ProvidingPosition,
ReceivingPosition,
};
use crate::utils::render_template; use crate::utils::render_template;
// Form structs for DeFi operations // Form structs for DeFi operations
@ -26,6 +29,7 @@ pub struct ReceivingForm {
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub struct LiquidityForm { pub struct LiquidityForm {
pub first_token: String, pub first_token: String,
pub first_amount: f64, pub first_amount: f64,
@ -35,6 +39,7 @@ pub struct LiquidityForm {
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub struct StakingForm { pub struct StakingForm {
pub asset_id: String, pub asset_id: String,
pub amount: f64, pub amount: f64,
@ -49,6 +54,7 @@ pub struct SwapForm {
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub struct CollateralForm { pub struct CollateralForm {
pub asset_id: String, pub asset_id: String,
pub amount: f64, pub amount: f64,
@ -63,29 +69,29 @@ impl DefiController {
// Display the DeFi dashboard // Display the DeFi dashboard
pub async fn index(tmpl: web::Data<Tera>, req: HttpRequest) -> Result<HttpResponse> { pub async fn index(tmpl: web::Data<Tera>, req: HttpRequest) -> Result<HttpResponse> {
let mut context = Context::new(); let mut context = Context::new();
println!("DEBUG: Starting DeFi dashboard rendering"); println!("DEBUG: Starting DeFi dashboard rendering");
// Get mock assets for the dropdown selectors // Get mock assets for the dropdown selectors
let assets = Self::get_mock_assets(); let assets = Self::get_mock_assets();
println!("DEBUG: Generated {} mock assets", assets.len()); println!("DEBUG: Generated {} mock assets", assets.len());
// Add active_page for navigation highlighting // Add active_page for navigation highlighting
context.insert("active_page", &"defi"); context.insert("active_page", &"defi");
// Add DeFi stats // Add DeFi stats
let defi_stats = Self::get_defi_stats(); let defi_stats = Self::get_defi_stats();
context.insert("defi_stats", &serde_json::to_value(defi_stats).unwrap()); context.insert("defi_stats", &serde_json::to_value(defi_stats).unwrap());
// Add recent assets for selection in forms // Add recent assets for selection in forms
let recent_assets: Vec<serde_json::Map<String, serde_json::Value>> = assets let recent_assets: Vec<serde_json::Map<String, serde_json::Value>> = assets
.iter() .iter()
.take(5) .take(5)
.map(|a| Self::asset_to_json(a)) .map(|a| Self::asset_to_json(a))
.collect(); .collect();
context.insert("recent_assets", &recent_assets); context.insert("recent_assets", &recent_assets);
// Get user's providing positions // Get user's providing positions
let db = DEFI_DB.lock().unwrap(); let db = DEFI_DB.lock().unwrap();
let providing_positions = db.get_user_providing_positions("user123"); let providing_positions = db.get_user_providing_positions("user123");
@ -94,7 +100,7 @@ impl DefiController {
.map(|p| serde_json::to_value(p).unwrap()) .map(|p| serde_json::to_value(p).unwrap())
.collect(); .collect();
context.insert("providing_positions", &providing_positions_json); context.insert("providing_positions", &providing_positions_json);
// Get user's receiving positions // Get user's receiving positions
let receiving_positions = db.get_user_receiving_positions("user123"); let receiving_positions = db.get_user_receiving_positions("user123");
let receiving_positions_json: Vec<serde_json::Value> = receiving_positions let receiving_positions_json: Vec<serde_json::Value> = receiving_positions
@ -102,27 +108,30 @@ impl DefiController {
.map(|p| serde_json::to_value(p).unwrap()) .map(|p| serde_json::to_value(p).unwrap())
.collect(); .collect();
context.insert("receiving_positions", &receiving_positions_json); context.insert("receiving_positions", &receiving_positions_json);
// Add success message if present in query params // Add success message if present in query params
if let Some(success) = req.query_string().strip_prefix("success=") { if let Some(success) = req.query_string().strip_prefix("success=") {
let decoded = urlencoding::decode(success).unwrap_or_else(|_| success.into()); let decoded = urlencoding::decode(success).unwrap_or_else(|_| success.into());
context.insert("success_message", &decoded); context.insert("success_message", &decoded);
} }
println!("DEBUG: Rendering DeFi dashboard template"); println!("DEBUG: Rendering DeFi dashboard template");
let response = render_template(&tmpl, "defi/index.html", &context); let response = render_template(&tmpl, "defi/index.html", &context);
println!("DEBUG: Finished rendering DeFi dashboard template"); println!("DEBUG: Finished rendering DeFi dashboard template");
response response
} }
// Process providing request // 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); println!("DEBUG: Processing providing request: {:?}", form);
// Get the asset obligationails (in a real app, this would come from a database) // Get the asset obligationails (in a real app, this would come from a database)
let assets = Self::get_mock_assets(); let assets = Self::get_mock_assets();
let asset = assets.iter().find(|a| a.id == form.asset_id); let asset = assets.iter().find(|a| a.id == form.asset_id);
if let Some(asset) = asset { if let Some(asset) = asset {
// Calculate profit share and return amount // Calculate profit share and return amount
let profit_share = match form.duration { let profit_share = match form.duration {
@ -133,9 +142,10 @@ impl DefiController {
365 => 12.0, 365 => 12.0,
_ => 4.2, // Default to 30 days rate _ => 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 // Create a new providing position
let providing_position = ProvidingPosition { let providing_position = ProvidingPosition {
base: DefiPosition { base: DefiPosition {
@ -156,17 +166,23 @@ impl DefiController {
profit_share_earned: profit_share, profit_share_earned: profit_share,
return_amount, return_amount,
}; };
// Add the position to the database // Add the position to the database
{ {
let mut db = DEFI_DB.lock().unwrap(); let mut db = DEFI_DB.lock().unwrap();
db.add_providing_position(providing_position); db.add_providing_position(providing_position);
} }
// Redirect with success message // 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() Ok(HttpResponse::SeeOther()
.append_header(("Location", format!("/defi?success={}", urlencoding::encode(&success_message)))) .append_header((
"Location",
format!("/defi?success={}", urlencoding::encode(&success_message)),
))
.finish()) .finish())
} else { } else {
// Asset not found, redirect with error // Asset not found, redirect with error
@ -175,15 +191,18 @@ impl DefiController {
.finish()) .finish())
} }
} }
// Process receiving request // 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); println!("DEBUG: Processing receiving request: {:?}", form);
// Get the asset obligationails (in a real app, this would come from a database) // Get the asset obligationails (in a real app, this would come from a database)
let assets = Self::get_mock_assets(); let assets = Self::get_mock_assets();
let collateral_asset = assets.iter().find(|a| a.id == form.collateral_asset_id); let collateral_asset = assets.iter().find(|a| a.id == form.collateral_asset_id);
if let Some(collateral_asset) = collateral_asset { if let Some(collateral_asset) = collateral_asset {
// Calculate profit share rate based on duration // Calculate profit share rate based on duration
let profit_share_rate = match form.duration { let profit_share_rate = match form.duration {
@ -194,15 +213,17 @@ impl DefiController {
365 => 10.0, 365 => 10.0,
_ => 5.0, // Default to 30 days rate _ => 5.0, // Default to 30 days rate
}; };
// Calculate profit share and total to repay // 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; let total_to_repay = form.amount + profit_share;
// Calculate collateral value and ratio // 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; let collateral_ratio = (collateral_value / form.amount) * 100.0;
// Create a new receiving position // Create a new receiving position
let receiving_position = ReceivingPosition { let receiving_position = ReceivingPosition {
base: DefiPosition { base: DefiPosition {
@ -230,18 +251,23 @@ impl DefiController {
total_to_repay, total_to_repay,
collateral_ratio, collateral_ratio,
}; };
// Add the position to the database // Add the position to the database
{ {
let mut db = DEFI_DB.lock().unwrap(); let mut db = DEFI_DB.lock().unwrap();
db.add_receiving_position(receiving_position); db.add_receiving_position(receiving_position);
} }
// Redirect with success message // Redirect with success message
let success_message = format!("Successfully borrowed {} ZDFZ using {} {} as collateral", let success_message = format!(
form.amount, form.collateral_amount, collateral_asset.name); "Successfully borrowed {} ZDFZ using {} {} as collateral",
form.amount, form.collateral_amount, collateral_asset.name
);
Ok(HttpResponse::SeeOther() Ok(HttpResponse::SeeOther()
.append_header(("Location", format!("/defi?success={}", urlencoding::encode(&success_message)))) .append_header((
"Location",
format!("/defi?success={}", urlencoding::encode(&success_message)),
))
.finish()) .finish())
} else { } else {
// Asset not found, redirect with error // Asset not found, redirect with error
@ -250,116 +276,202 @@ impl DefiController {
.finish()) .finish())
} }
} }
// Process liquidity provision // 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); println!("DEBUG: Processing liquidity provision: {:?}", form);
// In a real application, this would add liquidity to a pool in the database // 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 // For now, we'll just redirect back to the DeFi dashboard with a success message
let success_message = format!("Successfully added liquidity: {} {} and {} {}", let success_message = format!(
form.first_amount, form.first_token, form.second_amount, form.second_token); "Successfully added liquidity: {} {} and {} {}",
form.first_amount, form.first_token, form.second_amount, form.second_token
);
Ok(HttpResponse::SeeOther() Ok(HttpResponse::SeeOther()
.append_header(("Location", format!("/defi?success={}", urlencoding::encode(&success_message)))) .append_header((
"Location",
format!("/defi?success={}", urlencoding::encode(&success_message)),
))
.finish()) .finish())
} }
// Process staking request // 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); println!("DEBUG: Processing staking request: {:?}", form);
// In a real application, this would create a staking position in the database // 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 // 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); let success_message = format!("Successfully staked {} {}", form.amount, form.asset_id);
Ok(HttpResponse::SeeOther() Ok(HttpResponse::SeeOther()
.append_header(("Location", format!("/defi?success={}", urlencoding::encode(&success_message)))) .append_header((
"Location",
format!("/defi?success={}", urlencoding::encode(&success_message)),
))
.finish()) .finish())
} }
// Process token swap // 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); println!("DEBUG: Processing token swap: {:?}", form);
// In a real application, this would perform a token swap in the database // 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 // For now, we'll just redirect back to the DeFi dashboard with a success message
let success_message = format!("Successfully swapped {} {} to {}", let success_message = format!(
form.from_amount, form.from_token, form.to_token); "Successfully swapped {} {} to {}",
form.from_amount, form.from_token, form.to_token
);
Ok(HttpResponse::SeeOther() Ok(HttpResponse::SeeOther()
.append_header(("Location", format!("/defi?success={}", urlencoding::encode(&success_message)))) .append_header((
"Location",
format!("/defi?success={}", urlencoding::encode(&success_message)),
))
.finish()) .finish())
} }
// Process collateral position creation // 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); println!("DEBUG: Processing collateral creation: {:?}", form);
// In a real application, this would create a collateral position in the database // 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 // For now, we'll just redirect back to the DeFi dashboard with a success message
let purpose_str = match form.purpose.as_str() { let purpose_str = match form.purpose.as_str() {
"funds" => "secure a funds", "funds" => "secure a funds",
"synthetic" => "generate synthetic assets", "synthetic" => "generate synthetic assets",
"leverage" => "leverage trading", "leverage" => "leverage trading",
_ => "collateralization", _ => "collateralization",
}; };
let success_message = format!("Successfully collateralized {} {} for {}", let success_message = format!(
form.amount, form.asset_id, purpose_str); "Successfully collateralized {} {} for {}",
form.amount, form.asset_id, purpose_str
);
Ok(HttpResponse::SeeOther() Ok(HttpResponse::SeeOther()
.append_header(("Location", format!("/defi?success={}", urlencoding::encode(&success_message)))) .append_header((
"Location",
format!("/defi?success={}", urlencoding::encode(&success_message)),
))
.finish()) .finish())
} }
// Helper method to get DeFi statistics // Helper method to get DeFi statistics
fn get_defi_stats() -> serde_json::Map<String, serde_json::Value> { fn get_defi_stats() -> serde_json::Map<String, serde_json::Value> {
let mut stats = serde_json::Map::new(); let mut stats = serde_json::Map::new();
// Handle Option<Number> by unwrapping with expect // 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(
stats.insert("providing_volume".to_string(), serde_json::Value::Number(serde_json::Number::from_f64(450000.0).expect("Valid float"))); "total_value_locked".to_string(),
stats.insert("receiving_volume".to_string(), serde_json::Value::Number(serde_json::Number::from_f64(320000.0).expect("Valid float"))); serde_json::Value::Number(
stats.insert("liquidity_pools_count".to_string(), serde_json::Value::Number(serde_json::Number::from(12))); serde_json::Number::from_f64(1250000.0).expect("Valid float"),
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(
"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 stats
} }
// Helper method to convert Asset to a JSON object for templates // Helper method to convert Asset to a JSON object for templates
fn asset_to_json(asset: &Asset) -> serde_json::Map<String, serde_json::Value> { fn asset_to_json(asset: &Asset) -> serde_json::Map<String, serde_json::Value> {
let mut map = serde_json::Map::new(); let mut map = serde_json::Map::new();
map.insert("id".to_string(), serde_json::Value::String(asset.id.clone())); map.insert(
map.insert("name".to_string(), serde_json::Value::String(asset.name.clone())); "id".to_string(),
map.insert("description".to_string(), serde_json::Value::String(asset.description.clone())); serde_json::Value::String(asset.id.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(
"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 // Add current valuation
if let Some(latest) = asset.latest_valuation() { if let Some(latest) = asset.latest_valuation() {
if let Some(num) = serde_json::Number::from_f64(latest.value) { 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 { } 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(
map.insert("valuation_date".to_string(), serde_json::Value::String(latest.date.format("%Y-%m-%d").to_string())); "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 { } else {
map.insert("current_valuation".to_string(), serde_json::Value::Number(serde_json::Number::from(0))); map.insert(
map.insert("valuation_currency".to_string(), serde_json::Value::String("USD".to_string())); "current_valuation".to_string(),
map.insert("valuation_date".to_string(), serde_json::Value::String("N/A".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 map
} }
// Generate mock assets for testing // Generate mock assets for testing
fn get_mock_assets() -> Vec<Asset> { fn get_mock_assets() -> Vec<Asset> {
// Reuse the asset controller's mock data function // Reuse the asset controller's mock data function

View File

@ -609,6 +609,7 @@ impl FlowController {
/// Form for creating a new flow /// Form for creating a new flow
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub struct FlowForm { pub struct FlowForm {
/// Flow name /// Flow name
pub name: String, pub name: String,
@ -620,6 +621,7 @@ pub struct FlowForm {
/// Form for marking a step as stuck /// Form for marking a step as stuck
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub struct StuckForm { pub struct StuckForm {
/// Reason for being stuck /// Reason for being stuck
pub reason: String, pub reason: String,
@ -627,6 +629,7 @@ pub struct StuckForm {
/// Form for adding a log to a step /// Form for adding a log to a step
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub struct LogForm { pub struct LogForm {
/// Log message /// Log message
pub message: String, pub message: String,

View File

@ -13,6 +13,7 @@ use chrono::prelude::*;
/// Controller for handling governance-related routes /// Controller for handling governance-related routes
pub struct GovernanceController; pub struct GovernanceController;
#[allow(dead_code)]
impl GovernanceController { impl GovernanceController {
/// Helper function to get user from session /// Helper function to get user from session
/// For testing purposes, this will always return a mock user /// 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 /// Represents the data submitted in the vote form
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub struct VoteForm { pub struct VoteForm {
/// Type of vote (yes, no, abstain) /// Type of vote (yes, no, abstain)
pub vote_type: String, pub vote_type: String,

View File

@ -96,6 +96,7 @@ impl HomeController {
/// Represents the data submitted in the contact form /// Represents the data submitted in the contact form
#[derive(Debug, serde::Deserialize)] #[derive(Debug, serde::Deserialize)]
#[allow(dead_code)]
pub struct ContactForm { pub struct ContactForm {
pub name: String, pub name: String,
pub email: String, pub email: String,

View File

@ -1,12 +1,11 @@
use actix_web::{web, HttpResponse, Result, http}; use actix_web::{HttpResponse, Result, http, web};
use tera::{Context, Tera}; use chrono::{Duration, Utc};
use chrono::{Utc, Duration};
use serde::Deserialize; 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::controllers::asset::AssetController;
use crate::models::asset::{Asset, AssetStatus, AssetType};
use crate::models::marketplace::{Listing, ListingStatus, ListingType, MarketplaceStatistics};
use crate::utils::render_template; use crate::utils::render_template;
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@ -22,6 +21,7 @@ pub struct ListingForm {
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub struct BidForm { pub struct BidForm {
pub amount: f64, pub amount: f64,
pub currency: String, pub currency: String,
@ -38,30 +38,33 @@ impl MarketplaceController {
// Display the marketplace dashboard // Display the marketplace dashboard
pub async fn index(tmpl: web::Data<Tera>) -> Result<HttpResponse> { pub async fn index(tmpl: web::Data<Tera>) -> Result<HttpResponse> {
let mut context = Context::new(); let mut context = Context::new();
let listings = Self::get_mock_listings(); let listings = Self::get_mock_listings();
let stats = MarketplaceStatistics::new(&listings); let stats = MarketplaceStatistics::new(&listings);
// Get featured listings (up to 4) // 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) .filter(|l| l.featured && l.status == ListingStatus::Active)
.take(4) .take(4)
.collect(); .collect();
// Get recent listings (up to 8) // 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) .filter(|l| l.status == ListingStatus::Active)
.collect(); .collect();
// Sort by created_at (newest first) // Sort by created_at (newest first)
recent_listings.sort_by(|a, b| b.created_at.cmp(&a.created_at)); recent_listings.sort_by(|a, b| b.created_at.cmp(&a.created_at));
let recent_listings = recent_listings.into_iter().take(8).collect::<Vec<_>>(); let recent_listings = recent_listings.into_iter().take(8).collect::<Vec<_>>();
// Get recent sales (up to 5) // 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) .filter(|l| l.status == ListingStatus::Sold)
.collect(); .collect();
// Sort by sold_at (newest first) // Sort by sold_at (newest first)
recent_sales.sort_by(|a, b| { recent_sales.sort_by(|a, b| {
let a_sold = a.sold_at.unwrap_or(a.created_at); let a_sold = a.sold_at.unwrap_or(a.created_at);
@ -69,88 +72,101 @@ impl MarketplaceController {
b_sold.cmp(&a_sold) b_sold.cmp(&a_sold)
}); });
let recent_sales = recent_sales.into_iter().take(5).collect::<Vec<_>>(); let recent_sales = recent_sales.into_iter().take(5).collect::<Vec<_>>();
// Add data to context // Add data to context
context.insert("active_page", &"marketplace"); context.insert("active_page", &"marketplace");
context.insert("stats", &stats); context.insert("stats", &stats);
context.insert("featured_listings", &featured_listings); context.insert("featured_listings", &featured_listings);
context.insert("recent_listings", &recent_listings); context.insert("recent_listings", &recent_listings);
context.insert("recent_sales", &recent_sales); context.insert("recent_sales", &recent_sales);
render_template(&tmpl, "marketplace/index.html", &context) render_template(&tmpl, "marketplace/index.html", &context)
} }
// Display all marketplace listings // Display all marketplace listings
pub async fn list_listings(tmpl: web::Data<Tera>) -> Result<HttpResponse> { pub async fn list_listings(tmpl: web::Data<Tera>) -> Result<HttpResponse> {
let mut context = Context::new(); let mut context = Context::new();
let listings = Self::get_mock_listings(); let listings = Self::get_mock_listings();
// Filter active listings // Filter active listings
let active_listings: Vec<&Listing> = listings.iter() let active_listings: Vec<&Listing> = listings
.iter()
.filter(|l| l.status == ListingStatus::Active) .filter(|l| l.status == ListingStatus::Active)
.collect(); .collect();
context.insert("active_page", &"marketplace"); context.insert("active_page", &"marketplace");
context.insert("listings", &active_listings); context.insert("listings", &active_listings);
context.insert("listing_types", &[ context.insert(
ListingType::FixedPrice.as_str(), "listing_types",
ListingType::Auction.as_str(), &[
ListingType::Exchange.as_str(), ListingType::FixedPrice.as_str(),
]); ListingType::Auction.as_str(),
context.insert("asset_types", &[ ListingType::Exchange.as_str(),
AssetType::Token.as_str(), ],
AssetType::Artwork.as_str(), );
AssetType::RealEstate.as_str(), context.insert(
AssetType::IntellectualProperty.as_str(), "asset_types",
AssetType::Commodity.as_str(), &[
AssetType::Share.as_str(), AssetType::Token.as_str(),
AssetType::Bond.as_str(), AssetType::Artwork.as_str(),
AssetType::Other.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) render_template(&tmpl, "marketplace/listings.html", &context)
} }
// Display my listings // Display my listings
pub async fn my_listings(tmpl: web::Data<Tera>) -> Result<HttpResponse> { pub async fn my_listings(tmpl: web::Data<Tera>) -> Result<HttpResponse> {
let mut context = Context::new(); let mut context = Context::new();
let listings = Self::get_mock_listings(); let listings = Self::get_mock_listings();
// Filter by current user (mock user ID) // Filter by current user (mock user ID)
let user_id = "user-123"; let user_id = "user-123";
let my_listings: Vec<&Listing> = listings.iter() let my_listings: Vec<&Listing> =
.filter(|l| l.seller_id == user_id) listings.iter().filter(|l| l.seller_id == user_id).collect();
.collect();
context.insert("active_page", &"marketplace"); context.insert("active_page", &"marketplace");
context.insert("listings", &my_listings); context.insert("listings", &my_listings);
render_template(&tmpl, "marketplace/my_listings.html", &context) render_template(&tmpl, "marketplace/my_listings.html", &context)
} }
// Display listing details // 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 listing_id = path.into_inner();
let mut context = Context::new(); let mut context = Context::new();
let listings = Self::get_mock_listings(); let listings = Self::get_mock_listings();
// Find the listing // Find the listing
let listing = listings.iter().find(|l| l.id == listing_id); let listing = listings.iter().find(|l| l.id == listing_id);
if let Some(listing) = listing { if let Some(listing) = listing {
// Get similar listings (same asset type, active) // Get similar listings (same asset type, active)
let similar_listings: Vec<&Listing> = listings.iter() let similar_listings: Vec<&Listing> = listings
.filter(|l| l.asset_type == listing.asset_type && .iter()
l.status == ListingStatus::Active && .filter(|l| {
l.id != listing.id) l.asset_type == listing.asset_type
&& l.status == ListingStatus::Active
&& l.id != listing.id
})
.take(4) .take(4)
.collect(); .collect();
// Get highest bid amount and minimum bid for auction listings // 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() { if let Some(bid) = listing.highest_bid() {
(Some(bid.amount), bid.amount + 1.0) (Some(bid.amount), bid.amount + 1.0)
} else { } else {
@ -159,74 +175,79 @@ impl MarketplaceController {
} else { } else {
(None, 0.0) (None, 0.0)
}; };
context.insert("active_page", &"marketplace"); context.insert("active_page", &"marketplace");
context.insert("listing", listing); context.insert("listing", listing);
context.insert("similar_listings", &similar_listings); context.insert("similar_listings", &similar_listings);
context.insert("highest_bid_amount", &highest_bid_amount); context.insert("highest_bid_amount", &highest_bid_amount);
context.insert("minimum_bid", &minimum_bid); context.insert("minimum_bid", &minimum_bid);
// Add current user info for bid/purchase forms // Add current user info for bid/purchase forms
let user_id = "user-123"; let user_id = "user-123";
let user_name = "Alice Hostly"; let user_name = "Alice Hostly";
context.insert("user_id", &user_id); context.insert("user_id", &user_id);
context.insert("user_name", &user_name); context.insert("user_name", &user_name);
render_template(&tmpl, "marketplace/listing_detail.html", &context) render_template(&tmpl, "marketplace/listing_detail.html", &context)
} else { } else {
Ok(HttpResponse::NotFound().finish()) Ok(HttpResponse::NotFound().finish())
} }
} }
// Display create listing form // Display create listing form
pub async fn create_listing_form(tmpl: web::Data<Tera>) -> Result<HttpResponse> { pub async fn create_listing_form(tmpl: web::Data<Tera>) -> Result<HttpResponse> {
let mut context = Context::new(); let mut context = Context::new();
// Get user's assets for selection // Get user's assets for selection
let assets = AssetController::get_mock_assets(); let assets = AssetController::get_mock_assets();
let user_id = "user-123"; // Mock user ID 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) .filter(|a| a.owner_id == user_id && a.status == AssetStatus::Active)
.collect(); .collect();
context.insert("active_page", &"marketplace"); context.insert("active_page", &"marketplace");
context.insert("assets", &user_assets); context.insert("assets", &user_assets);
context.insert("listing_types", &[ context.insert(
ListingType::FixedPrice.as_str(), "listing_types",
ListingType::Auction.as_str(), &[
ListingType::Exchange.as_str(), ListingType::FixedPrice.as_str(),
]); ListingType::Auction.as_str(),
ListingType::Exchange.as_str(),
],
);
render_template(&tmpl, "marketplace/create_listing.html", &context) render_template(&tmpl, "marketplace/create_listing.html", &context)
} }
// Create a new listing // Create a new listing
pub async fn create_listing( pub async fn create_listing(
tmpl: web::Data<Tera>, tmpl: web::Data<Tera>,
form: web::Form<ListingForm>, form: web::Form<ListingForm>,
) -> Result<HttpResponse> { ) -> Result<HttpResponse> {
let form = form.into_inner(); let form = form.into_inner();
// Get the asset details // Get the asset details
let assets = AssetController::get_mock_assets(); let assets = AssetController::get_mock_assets();
let asset = assets.iter().find(|a| a.id == form.asset_id); let asset = assets.iter().find(|a| a.id == form.asset_id);
if let Some(asset) = asset { if let Some(asset) = asset {
// Process tags // Process tags
let tags = match form.tags { let tags = match form.tags {
Some(tags_str) => tags_str.split(',') Some(tags_str) => tags_str
.split(',')
.map(|s| s.trim().to_string()) .map(|s| s.trim().to_string())
.filter(|s| !s.is_empty()) .filter(|s| !s.is_empty())
.collect(), .collect(),
None => Vec::new(), None => Vec::new(),
}; };
// Calculate expiration date if provided // Calculate expiration date if provided
let expires_at = form.duration_days.map(|days| { let expires_at = form
Utc::now() + Duration::days(days as i64) .duration_days
}); .map(|days| Utc::now() + Duration::days(days as i64));
// Parse listing type // Parse listing type
let listing_type = match form.listing_type.as_str() { let listing_type = match form.listing_type.as_str() {
"Fixed Price" => ListingType::FixedPrice, "Fixed Price" => ListingType::FixedPrice,
@ -234,11 +255,11 @@ impl MarketplaceController {
"Exchange" => ListingType::Exchange, "Exchange" => ListingType::Exchange,
_ => ListingType::FixedPrice, _ => ListingType::FixedPrice,
}; };
// Mock user data // Mock user data
let user_id = "user-123"; let user_id = "user-123";
let user_name = "Alice Hostly"; let user_name = "Alice Hostly";
// Create the listing // Create the listing
let _listing = Listing::new( let _listing = Listing::new(
form.title, form.title,
@ -255,9 +276,9 @@ impl MarketplaceController {
tags, tags,
asset.image_url.clone(), asset.image_url.clone(),
); );
// In a real application, we would save the listing to a database here // In a real application, we would save the listing to a database here
// Redirect to the marketplace // Redirect to the marketplace
Ok(HttpResponse::SeeOther() Ok(HttpResponse::SeeOther()
.insert_header((http::header::LOCATION, "/marketplace")) .insert_header((http::header::LOCATION, "/marketplace"))
@ -267,94 +288,101 @@ impl MarketplaceController {
let mut context = Context::new(); let mut context = Context::new();
context.insert("active_page", &"marketplace"); context.insert("active_page", &"marketplace");
context.insert("error", &"Asset not found"); context.insert("error", &"Asset not found");
render_template(&tmpl, "marketplace/create_listing.html", &context) render_template(&tmpl, "marketplace/create_listing.html", &context)
} }
} }
// Submit a bid on an auction listing // Submit a bid on an auction listing
#[allow(dead_code)]
pub async fn submit_bid( pub async fn submit_bid(
tmpl: web::Data<Tera>, _tmpl: web::Data<Tera>,
path: web::Path<String>, path: web::Path<String>,
form: web::Form<BidForm>, _form: web::Form<BidForm>,
) -> Result<HttpResponse> { ) -> Result<HttpResponse> {
let listing_id = path.into_inner(); let listing_id = path.into_inner();
let form = form.into_inner(); let _form = _form.into_inner();
// In a real application, we would: // In a real application, we would:
// 1. Find the listing in the database // 1. Find the listing in the database
// 2. Validate the bid // 2. Validate the bid
// 3. Create the bid // 3. Create the bid
// 4. Save it to the database // 4. Save it to the database
// For now, we'll just redirect back to the listing // For now, we'll just redirect back to the listing
Ok(HttpResponse::SeeOther() Ok(HttpResponse::SeeOther()
.insert_header((http::header::LOCATION, format!("/marketplace/{}", listing_id))) .insert_header((
http::header::LOCATION,
format!("/marketplace/{}", listing_id),
))
.finish()) .finish())
} }
// Purchase a fixed-price listing // Purchase a fixed-price listing
pub async fn purchase_listing( pub async fn purchase_listing(
tmpl: web::Data<Tera>, _tmpl: web::Data<Tera>,
path: web::Path<String>, path: web::Path<String>,
form: web::Form<PurchaseForm>, form: web::Form<PurchaseForm>,
) -> Result<HttpResponse> { ) -> Result<HttpResponse> {
let listing_id = path.into_inner(); let listing_id = path.into_inner();
let form = form.into_inner(); let form = form.into_inner();
if !form.agree_to_terms { if !form.agree_to_terms {
// User must agree to terms // User must agree to terms
return Ok(HttpResponse::SeeOther() return Ok(HttpResponse::SeeOther()
.insert_header((http::header::LOCATION, format!("/marketplace/{}", listing_id))) .insert_header((
http::header::LOCATION,
format!("/marketplace/{}", listing_id),
))
.finish()); .finish());
} }
// In a real application, we would: // In a real application, we would:
// 1. Find the listing in the database // 1. Find the listing in the database
// 2. Validate the purchase // 2. Validate the purchase
// 3. Process the transaction // 3. Process the transaction
// 4. Update the listing status // 4. Update the listing status
// 5. Transfer the asset // 5. Transfer the asset
// For now, we'll just redirect to the marketplace // For now, we'll just redirect to the marketplace
Ok(HttpResponse::SeeOther() Ok(HttpResponse::SeeOther()
.insert_header((http::header::LOCATION, "/marketplace")) .insert_header((http::header::LOCATION, "/marketplace"))
.finish()) .finish())
} }
// Cancel a listing // Cancel a listing
pub async fn cancel_listing( pub async fn cancel_listing(
tmpl: web::Data<Tera>, _tmpl: web::Data<Tera>,
path: web::Path<String>, path: web::Path<String>,
) -> Result<HttpResponse> { ) -> Result<HttpResponse> {
let _listing_id = path.into_inner(); let _listing_id = path.into_inner();
// In a real application, we would: // In a real application, we would:
// 1. Find the listing in the database // 1. Find the listing in the database
// 2. Validate that the current user is the seller // 2. Validate that the current user is the seller
// 3. Update the listing status // 3. Update the listing status
// For now, we'll just redirect to my listings // For now, we'll just redirect to my listings
Ok(HttpResponse::SeeOther() Ok(HttpResponse::SeeOther()
.insert_header((http::header::LOCATION, "/marketplace/my")) .insert_header((http::header::LOCATION, "/marketplace/my"))
.finish()) .finish())
} }
// Generate mock listings for development // Generate mock listings for development
pub fn get_mock_listings() -> Vec<Listing> { pub fn get_mock_listings() -> Vec<Listing> {
let assets = AssetController::get_mock_assets(); let assets = AssetController::get_mock_assets();
let mut listings = Vec::new(); let mut listings = Vec::new();
// Mock user data // Mock user data
let user_ids = vec!["user-123", "user-456", "user-789"]; let user_ids = vec!["user-123", "user-456", "user-789"];
let user_names = vec!["Alice Hostly", "Ethan Cloudman", "Priya Servera"]; let user_names = vec!["Alice Hostly", "Ethan Cloudman", "Priya Servera"];
// Create some fixed price listings // Create some fixed price listings
for i in 0..6 { for i in 0..6 {
let asset_index = i % assets.len(); let asset_index = i % assets.len();
let asset = &assets[asset_index]; let asset = &assets[asset_index];
let user_index = i % user_ids.len(); let user_index = i % user_ids.len();
let price = match asset.asset_type { let price = match asset.asset_type {
AssetType::Token => 50.0 + (i as f64 * 10.0), AssetType::Token => 50.0 + (i as f64 * 10.0),
AssetType::Artwork => 500.0 + (i as f64 * 100.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::Bond => 1500.0 + (i as f64 * 300.0),
AssetType::Other => 800.0 + (i as f64 * 150.0), AssetType::Other => 800.0 + (i as f64 * 150.0),
}; };
let mut listing = Listing::new( let mut listing = Listing::new(
format!("{} for Sale", asset.name), 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.id.clone(),
asset.name.clone(), asset.name.clone(),
asset.asset_type.clone(), asset.asset_type.clone(),
@ -381,21 +412,21 @@ impl MarketplaceController {
vec!["digital".to_string(), "asset".to_string()], vec!["digital".to_string(), "asset".to_string()],
asset.image_url.clone(), asset.image_url.clone(),
); );
// Make some listings featured // Make some listings featured
if i % 5 == 0 { if i % 5 == 0 {
listing.set_featured(true); listing.set_featured(true);
} }
listings.push(listing); listings.push(listing);
} }
// Create some auction listings // Create some auction listings
for i in 0..4 { for i in 0..4 {
let asset_index = (i + 6) % assets.len(); let asset_index = (i + 6) % assets.len();
let asset = &assets[asset_index]; let asset = &assets[asset_index];
let user_index = i % user_ids.len(); let user_index = i % user_ids.len();
let starting_price = match asset.asset_type { let starting_price = match asset.asset_type {
AssetType::Token => 40.0 + (i as f64 * 5.0), AssetType::Token => 40.0 + (i as f64 * 5.0),
AssetType::Artwork => 400.0 + (i as f64 * 50.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::Bond => 1200.0 + (i as f64 * 250.0),
AssetType::Other => 600.0 + (i as f64 * 120.0), AssetType::Other => 600.0 + (i as f64 * 120.0),
}; };
let mut listing = Listing::new( let mut listing = Listing::new(
format!("Auction: {}", asset.name), format!("Auction: {}", asset.name),
format!("Bid on this amazing {}. {}", asset.name, asset.description), format!("Bid on this amazing {}. {}", asset.name, asset.description),
@ -422,12 +453,13 @@ impl MarketplaceController {
vec!["auction".to_string(), "bidding".to_string()], vec!["auction".to_string(), "bidding".to_string()],
asset.image_url.clone(), asset.image_url.clone(),
); );
// Add some bids to the auctions // Add some bids to the auctions
let num_bids = 2 + (i % 3); let num_bids = 2 + (i % 3);
for j in 0..num_bids { for j in 0..num_bids {
let bidder_index = (j + 1) % user_ids.len(); 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 bid_amount = starting_price * (1.0 + (0.1 * (j + 1) as f64));
let _ = listing.add_bid( let _ = listing.add_bid(
user_ids[bidder_index].to_string(), user_ids[bidder_index].to_string(),
@ -437,21 +469,21 @@ impl MarketplaceController {
); );
} }
} }
// Make some listings featured // Make some listings featured
if i % 3 == 0 { if i % 3 == 0 {
listing.set_featured(true); listing.set_featured(true);
} }
listings.push(listing); listings.push(listing);
} }
// Create some exchange listings // Create some exchange listings
for i in 0..3 { for i in 0..3 {
let asset_index = (i + 10) % assets.len(); let asset_index = (i + 10) % assets.len();
let asset = &assets[asset_index]; let asset = &assets[asset_index];
let user_index = i % user_ids.len(); let user_index = i % user_ids.len();
let value = match asset.asset_type { let value = match asset.asset_type {
AssetType::Token => 60.0 + (i as f64 * 15.0), AssetType::Token => 60.0 + (i as f64 * 15.0),
AssetType::Artwork => 600.0 + (i as f64 * 150.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::Bond => 1800.0 + (i as f64 * 350.0),
AssetType::Other => 1000.0 + (i as f64 * 200.0), AssetType::Other => 1000.0 + (i as f64 * 200.0),
}; };
let listing = Listing::new( let listing = Listing::new(
format!("Trade: {}", asset.name), 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.id.clone(),
asset.name.clone(), asset.name.clone(),
asset.asset_type.clone(), asset.asset_type.clone(),
user_ids[user_index].to_string(), user_ids[user_index].to_string(),
user_names[user_index].to_string(), user_names[user_index].to_string(),
value, // Estimated value for exchange value, // Estimated value for exchange
"USD".to_string(), "USD".to_string(),
ListingType::Exchange, ListingType::Exchange,
Some(Utc::now() + Duration::days(60)), Some(Utc::now() + Duration::days(60)),
vec!["exchange".to_string(), "trade".to_string()], vec!["exchange".to_string(), "trade".to_string()],
asset.image_url.clone(), asset.image_url.clone(),
); );
listings.push(listing); listings.push(listing);
} }
// Create some sold listings // Create some sold listings
for i in 0..5 { for i in 0..5 {
let asset_index = (i + 13) % assets.len(); let asset_index = (i + 13) % assets.len();
let asset = &assets[asset_index]; let asset = &assets[asset_index];
let seller_index = i % user_ids.len(); let seller_index = i % user_ids.len();
let buyer_index = (i + 1) % user_ids.len(); let buyer_index = (i + 1) % user_ids.len();
let price = match asset.asset_type { let price = match asset.asset_type {
AssetType::Token => 55.0 + (i as f64 * 12.0), AssetType::Token => 55.0 + (i as f64 * 12.0),
AssetType::Artwork => 550.0 + (i as f64 * 120.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::Bond => 1650.0 + (i as f64 * 330.0),
AssetType::Other => 900.0 + (i as f64 * 180.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( let mut listing = Listing::new(
format!("{} - SOLD", asset.name), format!("{} - SOLD", asset.name),
format!("This {} was sold recently.", asset.name), format!("This {} was sold recently.", asset.name),
@ -517,27 +552,27 @@ impl MarketplaceController {
vec!["sold".to_string()], vec!["sold".to_string()],
asset.image_url.clone(), asset.image_url.clone(),
); );
// Mark as sold // Mark as sold
let _ = listing.mark_as_sold( let _ = listing.mark_as_sold(
user_ids[buyer_index].to_string(), user_ids[buyer_index].to_string(),
user_names[buyer_index].to_string(), user_names[buyer_index].to_string(),
sale_price, sale_price,
); );
// Set sold date to be sometime in the past // Set sold date to be sometime in the past
let days_ago = i as i64 + 1; let days_ago = i as i64 + 1;
listing.sold_at = Some(Utc::now() - Duration::days(days_ago)); listing.sold_at = Some(Utc::now() - Duration::days(days_ago));
listings.push(listing); listings.push(listing);
} }
// Create a few cancelled listings // Create a few cancelled listings
for i in 0..2 { for i in 0..2 {
let asset_index = (i + 18) % assets.len(); let asset_index = (i + 18) % assets.len();
let asset = &assets[asset_index]; let asset = &assets[asset_index];
let user_index = i % user_ids.len(); let user_index = i % user_ids.len();
let price = match asset.asset_type { let price = match asset.asset_type {
AssetType::Token => 45.0 + (i as f64 * 8.0), AssetType::Token => 45.0 + (i as f64 * 8.0),
AssetType::Artwork => 450.0 + (i as f64 * 80.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::Bond => 1350.0 + (i as f64 * 270.0),
AssetType::Other => 750.0 + (i as f64 * 150.0), AssetType::Other => 750.0 + (i as f64 * 150.0),
}; };
let mut listing = Listing::new( let mut listing = Listing::new(
format!("{} - Cancelled", asset.name), format!("{} - Cancelled", asset.name),
format!("This listing for {} was cancelled.", asset.name), format!("This listing for {} was cancelled.", asset.name),
@ -564,13 +599,13 @@ impl MarketplaceController {
vec!["cancelled".to_string()], vec!["cancelled".to_string()],
asset.image_url.clone(), asset.image_url.clone(),
); );
// Cancel the listing // Cancel the listing
let _ = listing.cancel(); let _ = listing.cancel();
listings.push(listing); listings.push(listing);
} }
listings listings
} }
} }

View File

@ -112,6 +112,7 @@ pub struct Asset {
pub external_url: Option<String>, pub external_url: Option<String>,
} }
#[allow(dead_code)]
impl Asset { impl Asset {
/// Creates a new asset /// Creates a new asset
pub fn new( pub fn new(

View File

@ -85,6 +85,7 @@ pub struct ContractSigner {
pub comments: Option<String>, pub comments: Option<String>,
} }
#[allow(dead_code)]
impl ContractSigner { impl ContractSigner {
/// Creates a new contract signer /// Creates a new contract signer
pub fn new(name: String, email: String) -> Self { pub fn new(name: String, email: String) -> Self {
@ -123,6 +124,7 @@ pub struct ContractRevision {
pub comments: Option<String>, pub comments: Option<String>,
} }
#[allow(dead_code)]
impl ContractRevision { impl ContractRevision {
/// Creates a new contract revision /// Creates a new contract revision
pub fn new(version: u32, content: String, created_by: String, comments: Option<String>) -> Self { 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>>, pub toc: Option<Vec<TocItem>>,
} }
#[allow(dead_code)]
impl Contract { impl Contract {
/// Creates a new contract /// Creates a new contract
pub fn new(title: String, description: String, contract_type: ContractType, created_by: String, organization_id: Option<String>) -> Self { pub fn new(title: String, description: String, contract_type: ContractType, created_by: String, organization_id: Option<String>) -> Self {

View File

@ -14,6 +14,7 @@ pub enum DefiPositionStatus {
Cancelled Cancelled
} }
#[allow(dead_code)]
impl DefiPositionStatus { impl DefiPositionStatus {
pub fn as_str(&self) -> &str { pub fn as_str(&self) -> &str {
match self { match self {
@ -35,6 +36,7 @@ pub enum DefiPositionType {
Collateral, Collateral,
} }
#[allow(dead_code)]
impl DefiPositionType { impl DefiPositionType {
pub fn as_str(&self) -> &str { pub fn as_str(&self) -> &str {
match self { match self {
@ -95,6 +97,7 @@ pub struct DefiDatabase {
receiving_positions: HashMap<String, ReceivingPosition>, receiving_positions: HashMap<String, ReceivingPosition>,
} }
#[allow(dead_code)]
impl DefiDatabase { impl DefiDatabase {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {

View File

@ -110,6 +110,7 @@ pub struct FlowStep {
pub logs: Vec<FlowLog>, pub logs: Vec<FlowLog>,
} }
#[allow(dead_code)]
impl FlowStep { impl FlowStep {
/// Creates a new flow step /// Creates a new flow step
pub fn new(name: String, description: String, order: u32) -> Self { pub fn new(name: String, description: String, order: u32) -> Self {
@ -189,6 +190,7 @@ pub struct FlowLog {
pub timestamp: DateTime<Utc>, pub timestamp: DateTime<Utc>,
} }
#[allow(dead_code)]
impl FlowLog { impl FlowLog {
/// Creates a new flow log /// Creates a new flow log
pub fn new(message: String) -> Self { pub fn new(message: String) -> Self {
@ -231,6 +233,7 @@ pub struct Flow {
pub current_step: Option<FlowStep>, pub current_step: Option<FlowStep>,
} }
#[allow(dead_code)]
impl Flow { impl Flow {
/// Creates a new flow /// Creates a new flow
pub fn new(name: &str, description: &str, flow_type: FlowType, owner_id: &str, owner_name: &str) -> Self { pub fn new(name: &str, description: &str, flow_type: FlowType, owner_id: &str, owner_name: &str) -> Self {

View File

@ -75,6 +75,7 @@ pub struct Proposal {
pub voting_ends_at: Option<DateTime<Utc>>, pub voting_ends_at: Option<DateTime<Utc>>,
} }
#[allow(dead_code)]
impl Proposal { impl Proposal {
/// Creates a new proposal /// Creates a new proposal
pub fn new(creator_id: i32, creator_name: String, title: String, description: String) -> Self { 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>, pub updated_at: DateTime<Utc>,
} }
#[allow(dead_code)]
impl Vote { impl Vote {
/// Creates a new vote /// Creates a new vote
pub fn new(proposal_id: String, voter_id: i32, voter_name: String, vote_type: VoteType, comment: Option<String>) -> Self { 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, pub total_votes: usize,
} }
#[allow(dead_code)]
impl VotingResults { impl VotingResults {
/// Creates a new empty voting results object /// Creates a new empty voting results object
pub fn new(proposal_id: String) -> Self { pub fn new(proposal_id: String) -> Self {

View File

@ -1,7 +1,7 @@
use crate::models::asset::AssetType;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use uuid::Uuid; use uuid::Uuid;
use crate::models::asset::{Asset, AssetType};
/// Status of a marketplace listing /// Status of a marketplace listing
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
@ -12,6 +12,7 @@ pub enum ListingStatus {
Expired, Expired,
} }
#[allow(dead_code)]
impl ListingStatus { impl ListingStatus {
pub fn as_str(&self) -> &str { pub fn as_str(&self) -> &str {
match self { match self {
@ -63,6 +64,7 @@ pub enum BidStatus {
Cancelled, Cancelled,
} }
#[allow(dead_code)]
impl BidStatus { impl BidStatus {
pub fn as_str(&self) -> &str { pub fn as_str(&self) -> &str {
match self { match self {
@ -103,6 +105,7 @@ pub struct Listing {
pub image_url: Option<String>, pub image_url: Option<String>,
} }
#[allow(dead_code)]
impl Listing { impl Listing {
/// Creates a new listing /// Creates a new listing
pub fn new( pub fn new(
@ -150,7 +153,13 @@ impl Listing {
} }
/// Adds a bid to the 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 { if self.status != ListingStatus::Active {
return Err("Listing is not active".to_string()); return Err("Listing is not active".to_string());
} }
@ -160,7 +169,10 @@ impl Listing {
} }
if currency != self.currency { 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 // 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 /// Gets the highest bid on the listing
pub fn highest_bid(&self) -> Option<&Bid> { pub fn highest_bid(&self) -> Option<&Bid> {
self.bids.iter() self.bids
.iter()
.filter(|b| b.status == BidStatus::Active) .filter(|b| b.status == BidStatus::Active)
.max_by(|a, b| a.amount.partial_cmp(&b.amount).unwrap()) .max_by(|a, b| a.amount.partial_cmp(&b.amount).unwrap())
} }
/// Marks the listing as sold /// 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 { if self.status != ListingStatus::Active {
return Err("Listing is not active".to_string()); 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 listings_by_type = std::collections::HashMap::new();
let mut sales_by_asset_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) .filter(|l| l.status == ListingStatus::Active)
.count(); .count();
let sold_listings = listings.iter() let sold_listings = listings
.iter()
.filter(|l| l.status == ListingStatus::Sold) .filter(|l| l.status == ListingStatus::Sold)
.count(); .count();

View File

@ -1,17 +1,16 @@
// Export models // 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 asset;
pub mod marketplace; pub mod calendar;
pub mod contract;
pub mod defi; pub mod defi;
pub mod flow;
pub mod governance;
pub mod marketplace;
pub mod ticket;
pub mod user;
// Re-export models for easier imports // Re-export models for easier imports
pub use user::User;
pub use ticket::{Ticket, TicketComment, TicketStatus, TicketPriority};
pub use calendar::{CalendarEvent, CalendarViewMode}; pub use calendar::{CalendarEvent, CalendarViewMode};
pub use marketplace::{Listing, ListingStatus, ListingType, Bid, BidStatus, MarketplaceStatistics}; pub use defi::initialize_mock_data;
pub use defi::{DefiPosition, DefiPositionType, DefiPositionStatus, ProvidingPosition, ReceivingPosition, DEFI_DB, initialize_mock_data}; pub use ticket::{Ticket, TicketComment, TicketPriority, TicketStatus};
pub use user::User;

View File

@ -76,6 +76,7 @@ pub struct Ticket {
pub assigned_to: Option<i32>, pub assigned_to: Option<i32>,
} }
#[allow(dead_code)]
impl Ticket { impl Ticket {
/// Creates a new ticket /// Creates a new ticket
pub fn new(user_id: i32, title: String, description: String, priority: TicketPriority) -> Self { pub fn new(user_id: i32, title: String, description: String, priority: TicketPriority) -> Self {

View File

@ -4,6 +4,7 @@ use bcrypt::{hash, verify, DEFAULT_COST};
/// Represents a user in the system /// Represents a user in the system
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[allow(dead_code)]
pub struct User { pub struct User {
/// Unique identifier for the user /// Unique identifier for the user
pub id: Option<i32>, pub id: Option<i32>,
@ -31,6 +32,7 @@ pub enum UserRole {
Admin, Admin,
} }
#[allow(dead_code)]
impl User { impl User {
/// Creates a new user with default values /// Creates a new user with default values
pub fn new(name: String, email: String) -> Self { pub fn new(name: String, email: String) -> Self {
@ -125,6 +127,7 @@ impl User {
/// Represents user login credentials /// Represents user login credentials
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub struct LoginCredentials { pub struct LoginCredentials {
pub email: String, pub email: String,
pub password: String, pub password: String,
@ -132,6 +135,7 @@ pub struct LoginCredentials {
/// Represents user registration data /// Represents user registration data
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub struct RegistrationData { pub struct RegistrationData {
pub name: String, pub name: String,
pub email: String, pub email: String,

View File

@ -1,7 +1,7 @@
use actix_web::{error, Error, HttpResponse}; use actix_web::{Error, HttpResponse};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use tera::{self, Context, Function, Tera, Value};
use std::error::Error as StdError; use std::error::Error as StdError;
use tera::{self, Context, Function, Tera, Value};
// Export modules // Export modules
pub mod redis_service; pub mod redis_service;
@ -11,6 +11,7 @@ pub use redis_service::RedisCalendarService;
/// Error type for template rendering /// Error type for template rendering
#[derive(Debug)] #[derive(Debug)]
#[allow(dead_code)]
pub struct TemplateError { pub struct TemplateError {
pub message: String, pub message: String,
pub details: String, pub details: String,
@ -46,7 +47,7 @@ impl Function for NowFunction {
}; };
let now = Utc::now(); let now = Utc::now();
// Special case for just getting the year // Special case for just getting the year
if args.get("year").and_then(|v| v.as_bool()).unwrap_or(false) { if args.get("year").and_then(|v| v.as_bool()).unwrap_or(false) {
return Ok(Value::String(now.format("%Y").to_string())); return Ok(Value::String(now.format("%Y").to_string()));
@ -68,14 +69,10 @@ impl Function for FormatDateFunction {
None => { None => {
return Err(tera::Error::msg( return Err(tera::Error::msg(
"The 'timestamp' argument must be a valid timestamp", "The 'timestamp' argument must be a valid timestamp",
)) ));
} }
}, },
None => { None => return Err(tera::Error::msg("The 'timestamp' argument is required")),
return Err(tera::Error::msg(
"The 'timestamp' argument is required",
))
}
}; };
let format = match args.get("format") { let format = match args.get("format") {
@ -89,23 +86,21 @@ impl Function for FormatDateFunction {
// Convert timestamp to DateTime using the non-deprecated method // Convert timestamp to DateTime using the non-deprecated method
let datetime = match DateTime::from_timestamp(timestamp, 0) { let datetime = match DateTime::from_timestamp(timestamp, 0) {
Some(dt) => dt, Some(dt) => dt,
None => { None => return Err(tera::Error::msg("Failed to convert timestamp to datetime")),
return Err(tera::Error::msg(
"Failed to convert timestamp to datetime",
))
}
}; };
Ok(Value::String(datetime.format(format).to_string())) Ok(Value::String(datetime.format(format).to_string()))
} }
} }
/// Formats a date for display /// Formats a date for display
#[allow(dead_code)]
pub fn format_date(date: &DateTime<Utc>, format: &str) -> String { pub fn format_date(date: &DateTime<Utc>, format: &str) -> String {
date.format(format).to_string() date.format(format).to_string()
} }
/// Truncates a string to a maximum length and adds an ellipsis if truncated /// 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 { pub fn truncate_string(s: &str, max_length: usize) -> String {
if s.len() <= max_length { if s.len() <= max_length {
s.to_string() s.to_string()
@ -124,38 +119,41 @@ pub fn render_template(
ctx: &Context, ctx: &Context,
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, Error> {
println!("DEBUG: Attempting to render template: {}", template_name); println!("DEBUG: Attempting to render template: {}", template_name);
// Print all context keys for debugging // Print all context keys for debugging
let mut keys = Vec::new(); let mut keys = Vec::new();
for (key, _) in ctx.clone().into_json().as_object().unwrap().iter() { for (key, _) in ctx.clone().into_json().as_object().unwrap().iter() {
keys.push(key.clone()); keys.push(key.clone());
} }
println!("DEBUG: Context keys: {:?}", keys); println!("DEBUG: Context keys: {:?}", keys);
match tmpl.render(template_name, ctx) { match tmpl.render(template_name, ctx) {
Ok(content) => { Ok(content) => {
println!("DEBUG: Successfully rendered template: {}", template_name); println!("DEBUG: Successfully rendered template: {}", template_name);
Ok(HttpResponse::Ok().content_type("text/html").body(content)) Ok(HttpResponse::Ok().content_type("text/html").body(content))
}, }
Err(e) => { Err(e) => {
// Log the error with more details // 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); println!("DEBUG: Error details: {:?}", e);
// Print the error cause chain for better debugging // Print the error cause chain for better debugging
let mut current_error: Option<&dyn StdError> = Some(&e); let mut current_error: Option<&dyn StdError> = Some(&e);
let mut error_chain = Vec::new(); let mut error_chain = Vec::new();
while let Some(error) = current_error { while let Some(error) = current_error {
error_chain.push(format!("{}", error)); error_chain.push(format!("{}", error));
current_error = error.source(); current_error = error.source();
} }
println!("DEBUG: Error chain: {:?}", error_chain); println!("DEBUG: Error chain: {:?}", error_chain);
// Log the error // Log the error
log::error!("Template rendering error: {}", e); log::error!("Template rendering error: {}", e);
// Create a simple error response with more detailed information // Create a simple error response with more detailed information
let error_html = format!( let error_html = format!(
r#"<!DOCTYPE html> r#"<!DOCTYPE html>
@ -187,9 +185,9 @@ pub fn render_template(
e, e,
error_chain.join("\n") error_chain.join("\n")
); );
println!("DEBUG: Returning simple error page"); println!("DEBUG: Returning simple error page");
Ok(HttpResponse::InternalServerError() Ok(HttpResponse::InternalServerError()
.content_type("text/html") .content_type("text/html")
.body(error_html)) .body(error_html))
@ -207,4 +205,4 @@ mod tests {
assert_eq!(truncate_string("Hello, world!", 5), "Hello..."); assert_eq!(truncate_string("Hello, world!", 5), "Hello...");
assert_eq!(truncate_string("", 5), ""); assert_eq!(truncate_string("", 5), "");
} }
} }