This commit is contained in:
despiegk 2025-04-22 10:39:29 +04:00
parent 15b05cb599
commit 34594b95fa
10 changed files with 208 additions and 299 deletions

View File

@ -2,6 +2,7 @@ use actix_web::{web, HttpResponse, Responder, Result, http::header, cookie::Cook
use actix_session::Session; use actix_session::Session;
use tera::Tera; use tera::Tera;
use crate::models::user::{User, LoginCredentials, RegistrationData, UserRole}; use crate::models::user::{User, LoginCredentials, RegistrationData, UserRole};
use crate::utils::render_template;
use jsonwebtoken::{encode, decode, Header, Algorithm, Validation, EncodingKey, DecodingKey}; use jsonwebtoken::{encode, decode, Header, Algorithm, Validation, EncodingKey, DecodingKey};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use chrono::{Utc, Duration}; use chrono::{Utc, Duration};
@ -91,13 +92,7 @@ impl AuthController {
let mut ctx = tera::Context::new(); let mut ctx = tera::Context::new();
ctx.insert("active_page", "login"); ctx.insert("active_page", "login");
let rendered = tmpl.render("auth/login.html", &ctx) render_template(&tmpl, "auth/login.html", &ctx)
.map_err(|e| {
eprintln!("Template rendering error: {}", e);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
} }
/// Handles user login /// Handles user login
@ -146,13 +141,7 @@ impl AuthController {
let mut ctx = tera::Context::new(); let mut ctx = tera::Context::new();
ctx.insert("active_page", "register"); ctx.insert("active_page", "register");
let rendered = tmpl.render("auth/register.html", &ctx) render_template(&tmpl, "auth/register.html", &ctx)
.map_err(|e| {
eprintln!("Template rendering error: {}", e);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
} }
/// Handles user registration /// Handles user registration

View File

@ -6,7 +6,7 @@ use tera::Tera;
use serde_json::Value; use serde_json::Value;
use crate::models::{CalendarEvent, CalendarViewMode}; use crate::models::{CalendarEvent, CalendarViewMode};
use crate::utils::RedisCalendarService; use crate::utils::{RedisCalendarService, render_template};
/// Controller for handling calendar-related routes /// Controller for handling calendar-related routes
pub struct CalendarController; pub struct CalendarController;
@ -215,13 +215,7 @@ impl CalendarController {
}, },
} }
let rendered = tmpl.render("calendar/index.html", &ctx) render_template(&tmpl, "calendar/index.html", &ctx)
.map_err(|e| {
eprintln!("Template rendering error: {}", e);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
} }
/// Handles the new event page route /// Handles the new event page route
@ -234,13 +228,7 @@ impl CalendarController {
ctx.insert("user", &user); ctx.insert("user", &user);
} }
let rendered = tmpl.render("calendar/new_event.html", &ctx) render_template(&tmpl, "calendar/new_event.html", &ctx)
.map_err(|e| {
eprintln!("Template rendering error: {}", e);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
} }
/// Handles the create event route /// Handles the create event route
@ -298,13 +286,9 @@ impl CalendarController {
ctx.insert("user", &user); ctx.insert("user", &user);
} }
let rendered = tmpl.render("calendar/new_event.html", &ctx) let result = render_template(&tmpl, "calendar/new_event.html", &ctx)?;
.map_err(|e| {
eprintln!("Template rendering error: {}", e);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::InternalServerError().content_type("text/html").body(rendered)) Ok(HttpResponse::InternalServerError().content_type("text/html").body(result.into_body()))
} }
} }
} }

View File

@ -4,6 +4,7 @@ use chrono::{Utc, Duration};
use serde::Deserialize; use serde::Deserialize;
use crate::models::contract::{Contract, ContractStatus, ContractType, ContractStatistics, SignerStatus}; use crate::models::contract::{Contract, ContractStatus, ContractType, ContractStatistics, SignerStatus};
use crate::utils::render_template;
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct ContractForm { pub struct ContractForm {
@ -62,15 +63,7 @@ impl ContractController {
context.insert("draft_contracts", &draft_contracts); context.insert("draft_contracts", &draft_contracts);
let rendered = tmpl.render("contracts/index.html", &context) render_template(&tmpl, "contracts/index.html", &context)
.map_err(|e| {
log::error!("Template rendering error: {}", e);
log::error!("Error details: {:?}", e);
log::error!("Context: {:?}", context);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
} }
// Display the list of all contracts // Display the list of all contracts
@ -89,15 +82,7 @@ impl ContractController {
context.insert("contracts", &contracts_data); context.insert("contracts", &contracts_data);
context.insert("filter", &"all"); context.insert("filter", &"all");
let rendered = tmpl.render("contracts/contracts.html", &context) render_template(&tmpl, "contracts/contracts.html", &context)
.map_err(|e| {
log::error!("Template rendering error: {}", e);
log::error!("Error details: {:?}", e);
log::error!("Context: {:?}", context);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
} }
// Display the list of user's contracts // Display the list of user's contracts
@ -115,15 +100,7 @@ impl ContractController {
context.insert("contracts", &contracts_data); context.insert("contracts", &contracts_data);
let rendered = tmpl.render("contracts/my_contracts.html", &context) render_template(&tmpl, "contracts/my_contracts.html", &context)
.map_err(|e| {
log::error!("Template rendering error: {}", e);
log::error!("Error details: {:?}", e);
log::error!("Context: {:?}", context);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
} }
// Display a specific contract // Display a specific contract
@ -146,15 +123,7 @@ impl ContractController {
context.insert("contract", &contract_json); context.insert("contract", &contract_json);
context.insert("user_has_signed", &false); // Mock data context.insert("user_has_signed", &false); // Mock data
let rendered = tmpl.render("contracts/contract_detail.html", &context) render_template(&tmpl, "contracts/contract_detail.html", &context)
.map_err(|e| {
log::error!("Template rendering error: {}", e);
log::error!("Error details: {:?}", e);
log::error!("Context: {:?}", context);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
}, },
None => { None => {
Ok(HttpResponse::NotFound().finish()) Ok(HttpResponse::NotFound().finish())
@ -180,26 +149,18 @@ impl ContractController {
context.insert("contract_types", &contract_types); context.insert("contract_types", &contract_types);
let rendered = tmpl.render("contracts/create_contract.html", &context) render_template(&tmpl, "contracts/create_contract.html", &context)
.map_err(|e| {
log::error!("Template rendering error: {}", e);
log::error!("Error details: {:?}", e);
log::error!("Context: {:?}", context);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
} }
// 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>,
form: web::Form<ContractForm>, _form: web::Form<ContractForm>,
) -> Result<HttpResponse> { ) -> Result<HttpResponse> {
// 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().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

View File

@ -3,10 +3,10 @@ use actix_session::Session;
use chrono::{Utc, Duration}; use chrono::{Utc, Duration};
use serde::Deserialize; use serde::Deserialize;
use tera::Tera; use tera::Tera;
use std::error::Error; // Add this line
use crate::models::flow::{Flow, FlowStatus, FlowType, FlowStatistics, FlowStep, StepStatus, FlowLog}; use crate::models::flow::{Flow, FlowStatus, FlowType, FlowStatistics, FlowStep, StepStatus, FlowLog};
use crate::controllers::auth::Claims; use crate::controllers::auth::Claims;
use crate::utils::render_template;
pub struct FlowController; pub struct FlowController;
@ -25,15 +25,7 @@ impl FlowController {
ctx.insert("active_flows", &flows.iter().filter(|f| f.status == FlowStatus::InProgress).collect::<Vec<_>>()); ctx.insert("active_flows", &flows.iter().filter(|f| f.status == FlowStatus::InProgress).collect::<Vec<_>>());
ctx.insert("stuck_flows", &flows.iter().filter(|f| f.status == FlowStatus::Stuck).collect::<Vec<_>>()); ctx.insert("stuck_flows", &flows.iter().filter(|f| f.status == FlowStatus::Stuck).collect::<Vec<_>>());
let rendered = tmpl.render("flows/index.html", &ctx) render_template(&tmpl, "flows/index.html", &ctx)
.map_err(|e| {
log::error!("Template rendering error: {}", e);
log::error!("Error details: {:?}", e);
log::error!("Context: {:?}", ctx);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
} }
/// Renders the flows list page /// Renders the flows list page
@ -46,15 +38,7 @@ impl FlowController {
ctx.insert("user", &user); ctx.insert("user", &user);
ctx.insert("flows", &flows); ctx.insert("flows", &flows);
let rendered = tmpl.render("flows/flows.html", &ctx) render_template(&tmpl, "flows/flows.html", &ctx)
.map_err(|e| {
log::error!("Template rendering error: {}", e);
log::error!("Error details: {:?}", e);
log::error!("Context: {:?}", ctx);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
} }
/// Renders the flow detail page /// Renders the flow detail page
@ -76,29 +60,20 @@ impl FlowController {
ctx.insert("user", &user); ctx.insert("user", &user);
ctx.insert("flow", flow); ctx.insert("flow", flow);
let rendered = tmpl.render("flows/flow_detail.html", &ctx) render_template(&tmpl, "flows/flow_detail.html", &ctx)
.map_err(|e| {
log::error!("Template rendering error: {}", e);
log::error!("Error details: {:?}", e);
log::error!("Context: {:?}", ctx);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
} else { } else {
let mut ctx = tera::Context::new(); let mut ctx = tera::Context::new();
ctx.insert("active_page", "flows"); ctx.insert("active_page", "flows");
ctx.insert("error", "Flow not found"); ctx.insert("error", "Flow not found");
let rendered = tmpl.render("error.html", &ctx) // For the error page, we'll use a special case to set the status code to 404
.map_err(|e| { match tmpl.render("error.html", &ctx) {
log::error!("Template rendering error: {}", e); Ok(content) => Ok(HttpResponse::NotFound().content_type("text/html").body(content)),
log::error!("Error details: {:?}", e); Err(e) => {
log::error!("Context: {:?}", ctx); log::error!("Error rendering error template: {}", e);
actix_web::error::ErrorInternalServerError("Template rendering error") Err(actix_web::error::ErrorInternalServerError(format!("Error: {}", e)))
})?; }
}
Ok(HttpResponse::NotFound().content_type("text/html").body(rendered))
} }
} }
@ -110,21 +85,13 @@ impl FlowController {
ctx.insert("active_page", "flows"); ctx.insert("active_page", "flows");
ctx.insert("user", &user); ctx.insert("user", &user);
let rendered = tmpl.render("flows/create_flow.html", &ctx) render_template(&tmpl, "flows/create_flow.html", &ctx)
.map_err(|e| {
log::error!("Template rendering error: {}", e);
log::error!("Error details: {:?}", e);
log::error!("Context: {:?}", ctx);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
} }
/// Handles the create flow form submission /// Handles the create flow form submission
pub async fn create_flow( pub async fn create_flow(
_form: web::Form<FlowForm>, _form: web::Form<FlowForm>,
session: Session _session: Session
) -> impl Responder { ) -> impl Responder {
// In a real application, we would create a new flow here // In a real application, we would create a new flow here
// For now, just redirect to the flows list // For now, just redirect to the flows list
@ -149,15 +116,7 @@ impl FlowController {
ctx.insert("user", &user); ctx.insert("user", &user);
ctx.insert("flows", &my_flows); ctx.insert("flows", &my_flows);
let rendered = tmpl.render("flows/my_flows.html", &ctx) render_template(&tmpl, "flows/my_flows.html", &ctx)
.map_err(|e| {
log::error!("Template rendering error: {}", e);
log::error!("Error details: {:?}", e);
log::error!("Context: {:?}", ctx);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
} else { } else {
Ok(HttpResponse::Found() Ok(HttpResponse::Found()
.append_header(("Location", "/login")) .append_header(("Location", "/login"))

View File

@ -5,6 +5,7 @@ use serde_json::Value;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use chrono::{Utc, Duration}; use chrono::{Utc, Duration};
use crate::models::governance::{Proposal, Vote, ProposalStatus, VoteType, VotingResults}; use crate::models::governance::{Proposal, Vote, ProposalStatus, VoteType, VotingResults};
use crate::utils::render_template;
/// Controller for handling governance-related routes /// Controller for handling governance-related routes
pub struct GovernanceController; pub struct GovernanceController;
@ -35,13 +36,7 @@ impl GovernanceController {
let stats = Self::get_mock_statistics(); let stats = Self::get_mock_statistics();
ctx.insert("stats", &stats); ctx.insert("stats", &stats);
let rendered = tmpl.render("governance/index.html", &ctx) render_template(&tmpl, "governance/index.html", &ctx)
.map_err(|e| {
eprintln!("Template rendering error: {}", e);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
} }
/// Handles the proposal list page route /// Handles the proposal list page route
@ -59,13 +54,7 @@ impl GovernanceController {
let proposals = Self::get_mock_proposals(); let proposals = Self::get_mock_proposals();
ctx.insert("proposals", &proposals); ctx.insert("proposals", &proposals);
let rendered = tmpl.render("governance/proposals.html", &ctx) render_template(&tmpl, "governance/proposals.html", &ctx)
.map_err(|e| {
eprintln!("Template rendering error: {}", e);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
} }
/// Handles the proposal detail page route /// Handles the proposal detail page route
@ -96,23 +85,18 @@ impl GovernanceController {
let results = Self::get_mock_voting_results(&proposal_id); let results = Self::get_mock_voting_results(&proposal_id);
ctx.insert("results", &results); ctx.insert("results", &results);
let rendered = tmpl.render("governance/proposal_detail.html", &ctx) render_template(&tmpl, "governance/proposal_detail.html", &ctx)
.map_err(|e| {
eprintln!("Template rendering error: {}", e);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
} else { } else {
// Proposal not found // Proposal not found
ctx.insert("error", "Proposal not found"); ctx.insert("error", "Proposal not found");
let rendered = tmpl.render("error.html", &ctx) // For the error page, we'll use a special case to set the status code to 404
.map_err(|e| { match tmpl.render("error.html", &ctx) {
eprintln!("Template rendering error: {}", e); Ok(content) => Ok(HttpResponse::NotFound().content_type("text/html").body(content)),
actix_web::error::ErrorInternalServerError("Template rendering error") Err(e) => {
})?; eprintln!("Error rendering error template: {}", e);
Err(actix_web::error::ErrorInternalServerError(format!("Error: {}", e)))
Ok(HttpResponse::NotFound().content_type("text/html").body(rendered)) }
}
} }
} }
@ -130,18 +114,12 @@ impl GovernanceController {
return Ok(HttpResponse::Found().append_header(("Location", "/login")).finish()); return Ok(HttpResponse::Found().append_header(("Location", "/login")).finish());
} }
let rendered = tmpl.render("governance/create_proposal.html", &ctx) render_template(&tmpl, "governance/create_proposal.html", &ctx)
.map_err(|e| {
eprintln!("Template rendering error: {}", e);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
} }
/// Handles the submission of a new proposal /// Handles the submission of a new proposal
pub async fn submit_proposal( pub async fn submit_proposal(
form: web::Form<ProposalForm>, _form: web::Form<ProposalForm>,
tmpl: web::Data<Tera>, tmpl: web::Data<Tera>,
session: Session session: Session
) -> Result<impl Responder> { ) -> Result<impl Responder> {
@ -166,19 +144,13 @@ impl GovernanceController {
let proposals = Self::get_mock_proposals(); let proposals = Self::get_mock_proposals();
ctx.insert("proposals", &proposals); ctx.insert("proposals", &proposals);
let rendered = tmpl.render("governance/proposals.html", &ctx) render_template(&tmpl, "governance/proposals.html", &ctx)
.map_err(|e| {
eprintln!("Template rendering error: {}", e);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
} }
/// Handles the submission of a vote on a proposal /// Handles the submission of a vote on a proposal
pub async fn submit_vote( pub async fn submit_vote(
path: web::Path<String>, path: web::Path<String>,
form: web::Form<VoteForm>, _form: web::Form<VoteForm>,
tmpl: web::Data<Tera>, tmpl: web::Data<Tera>,
session: Session session: Session
) -> Result<impl Responder> { ) -> Result<impl Responder> {
@ -211,23 +183,18 @@ impl GovernanceController {
let results = Self::get_mock_voting_results(&proposal_id); let results = Self::get_mock_voting_results(&proposal_id);
ctx.insert("results", &results); ctx.insert("results", &results);
let rendered = tmpl.render("governance/proposal_detail.html", &ctx) render_template(&tmpl, "governance/proposal_detail.html", &ctx)
.map_err(|e| {
eprintln!("Template rendering error: {}", e);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
} else { } else {
// Proposal not found // Proposal not found
ctx.insert("error", "Proposal not found"); ctx.insert("error", "Proposal not found");
let rendered = tmpl.render("error.html", &ctx) // For the error page, we'll use a special case to set the status code to 404
.map_err(|e| { match tmpl.render("error.html", &ctx) {
eprintln!("Template rendering error: {}", e); Ok(content) => Ok(HttpResponse::NotFound().content_type("text/html").body(content)),
actix_web::error::ErrorInternalServerError("Template rendering error") Err(e) => {
})?; eprintln!("Error rendering error template: {}", e);
Err(actix_web::error::ErrorInternalServerError(format!("Error: {}", e)))
Ok(HttpResponse::NotFound().content_type("text/html").body(rendered)) }
}
} }
} }
@ -245,13 +212,7 @@ impl GovernanceController {
let votes = Self::get_mock_votes_for_user(1); // Assuming user ID 1 for mock data let votes = Self::get_mock_votes_for_user(1); // Assuming user ID 1 for mock data
ctx.insert("votes", &votes); ctx.insert("votes", &votes);
let rendered = tmpl.render("governance/my_votes.html", &ctx) render_template(&tmpl, "governance/my_votes.html", &ctx)
.map_err(|e| {
eprintln!("Template rendering error: {}", e);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
} else { } else {
// Redirect to login if not logged in // Redirect to login if not logged in
Ok(HttpResponse::Found().append_header(("Location", "/login")).finish()) Ok(HttpResponse::Found().append_header(("Location", "/login")).finish())

View File

@ -1,8 +1,10 @@
use actix_web::{web, HttpResponse, Responder, Result}; use actix_web::{web, Responder, Result};
use actix_session::Session; use actix_session::Session;
use tera::Tera; use tera::Tera;
use serde_json::Value; use serde_json::Value;
use crate::utils::render_template;
/// Controller for handling home-related routes /// Controller for handling home-related routes
pub struct HomeController; pub struct HomeController;
@ -24,13 +26,7 @@ impl HomeController {
ctx.insert("user", &user); ctx.insert("user", &user);
} }
let rendered = tmpl.render("editor.html", &ctx) render_template(&tmpl, "editor.html", &ctx)
.map_err(|e| {
eprintln!("Template rendering error: {}", e);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
} }
/// Handles the home page route /// Handles the home page route
@ -43,13 +39,7 @@ impl HomeController {
ctx.insert("user", &user); ctx.insert("user", &user);
} }
let rendered = tmpl.render("index.html", &ctx) render_template(&tmpl, "index.html", &ctx)
.map_err(|e| {
eprintln!("Template rendering error: {}", e);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
} }
/// Handles the about page route /// Handles the about page route
@ -62,13 +52,7 @@ impl HomeController {
ctx.insert("user", &user); ctx.insert("user", &user);
} }
let rendered = tmpl.render("about.html", &ctx) render_template(&tmpl, "about.html", &ctx)
.map_err(|e| {
eprintln!("Template rendering error: {}", e);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
} }
/// Handles the contact page route /// Handles the contact page route
@ -81,13 +65,7 @@ impl HomeController {
ctx.insert("user", &user); ctx.insert("user", &user);
} }
let rendered = tmpl.render("contact.html", &ctx) render_template(&tmpl, "contact.html", &ctx)
.map_err(|e| {
eprintln!("Template rendering error: {}", e);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
} }
/// Handles form submissions from the contact page /// Handles form submissions from the contact page
@ -112,13 +90,7 @@ impl HomeController {
ctx.insert("user", &user); ctx.insert("user", &user);
} }
let rendered = tmpl.render("contact.html", &ctx) render_template(&tmpl, "contact.html", &ctx)
.map_err(|e| {
eprintln!("Template rendering error: {}", e);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
} }
} }

View File

@ -4,6 +4,7 @@ use tera::Tera;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;
use crate::models::{User, Ticket, TicketComment, TicketStatus, TicketPriority}; use crate::models::{User, Ticket, TicketComment, TicketStatus, TicketPriority};
use crate::utils::render_template;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@ -131,13 +132,7 @@ impl TicketController {
]); ]);
// Render the template // Render the template
let rendered = tmpl.render("tickets/list.html", &ctx) render_template(&tmpl, "tickets/list.html", &ctx)
.map_err(|e| {
eprintln!("Template rendering error: {}", e);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
} }
/// Shows the form for creating a new ticket /// Shows the form for creating a new ticket
@ -172,13 +167,7 @@ impl TicketController {
]); ]);
// Render the template // Render the template
let rendered = tmpl.render("tickets/new.html", &ctx) render_template(&tmpl, "tickets/new.html", &ctx)
.map_err(|e| {
eprintln!("Template rendering error: {}", e);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
} }
/// Creates a new ticket /// Creates a new ticket
@ -285,13 +274,7 @@ impl TicketController {
]); ]);
// Render the template // Render the template
let rendered = tmpl.render("tickets/show.html", &ctx) render_template(&tmpl, "tickets/show.html", &ctx)
.map_err(|e| {
eprintln!("Template rendering error: {}", e);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
} }
/// Adds a comment to a ticket /// Adds a comment to a ticket
@ -443,12 +426,6 @@ impl TicketController {
ctx.insert("my_tickets", &true); ctx.insert("my_tickets", &true);
// Render the template // Render the template
let rendered = tmpl.render("tickets/list.html", &ctx) render_template(&tmpl, "tickets/list.html", &ctx)
.map_err(|e| {
eprintln!("Template rendering error: {}", e);
actix_web::error::ErrorInternalServerError("Template rendering error")
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
} }
} }

View File

@ -1,5 +1,6 @@
use actix_web::{error, Error, HttpResponse};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use tera::{self, Function, Result, Value}; use tera::{self, Context, Function, Tera, Value};
// Export modules // Export modules
pub mod redis_service; pub mod redis_service;
@ -7,6 +8,22 @@ pub mod redis_service;
// Re-export for easier imports // Re-export for easier imports
pub use redis_service::RedisCalendarService; pub use redis_service::RedisCalendarService;
/// Error type for template rendering
#[derive(Debug)]
pub struct TemplateError {
pub message: String,
pub details: String,
pub location: String,
}
impl std::fmt::Display for TemplateError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Template error in {}: {}", self.location, self.message)
}
}
impl std::error::Error for TemplateError {}
/// Registers custom Tera functions /// Registers custom Tera functions
pub fn register_tera_functions(tera: &mut tera::Tera) { pub fn register_tera_functions(tera: &mut tera::Tera) {
tera.register_function("now", NowFunction); tera.register_function("now", NowFunction);
@ -18,7 +35,7 @@ pub fn register_tera_functions(tera: &mut tera::Tera) {
pub struct NowFunction; pub struct NowFunction;
impl Function for NowFunction { impl Function for NowFunction {
fn call(&self, args: &std::collections::HashMap<String, Value>) -> Result<Value> { fn call(&self, args: &std::collections::HashMap<String, Value>) -> tera::Result<Value> {
let format = match args.get("format") { let format = match args.get("format") {
Some(val) => match val.as_str() { Some(val) => match val.as_str() {
Some(s) => s, Some(s) => s,
@ -43,7 +60,7 @@ impl Function for NowFunction {
pub struct FormatDateFunction; pub struct FormatDateFunction;
impl Function for FormatDateFunction { impl Function for FormatDateFunction {
fn call(&self, args: &std::collections::HashMap<String, Value>) -> Result<Value> { fn call(&self, args: &std::collections::HashMap<String, Value>) -> tera::Result<Value> {
let timestamp = match args.get("timestamp") { let timestamp = match args.get("timestamp") {
Some(val) => match val.as_i64() { Some(val) => match val.as_i64() {
Some(ts) => ts, Some(ts) => ts,
@ -96,6 +113,50 @@ pub fn truncate_string(s: &str, max_length: usize) -> String {
} }
} }
/// Renders a template with error handling
///
/// This function attempts to render a template and handles any errors by rendering
/// the error template with detailed error information.
pub fn render_template(
tmpl: &Tera,
template_name: &str,
ctx: &Context,
) -> Result<HttpResponse, Error> {
match tmpl.render(template_name, ctx) {
Ok(content) => Ok(HttpResponse::Ok().content_type("text/html").body(content)),
Err(e) => {
// Log the error
log::error!("Template rendering error: {}", e);
log::error!("Error details: {:?}", e);
log::error!("Context: {:?}", ctx);
// Create a context for the error template
let mut error_ctx = Context::new();
error_ctx.insert("error", &format!("Template rendering error: {}", e));
error_ctx.insert("error_details", &format!("{:?}", e));
error_ctx.insert("error_location", &template_name);
// Try to render the error template
match tmpl.render("error.html", &error_ctx) {
Ok(error_page) => {
// Return the error page with a 500 status code
Ok(HttpResponse::InternalServerError()
.content_type("text/html")
.body(error_page))
}
Err(render_err) => {
// If we can't render the error template, log it and return a basic error
log::error!("Error rendering error template: {}", render_err);
Err(error::ErrorInternalServerError(format!(
"Template rendering error: {}. Failed to render error page: {}",
e, render_err
)))
}
}
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -7,18 +7,44 @@
<div class="col-md-8"> <div class="col-md-8">
<div class="card border-danger"> <div class="card border-danger">
<div class="card-header bg-danger text-white"> <div class="card-header bg-danger text-white">
<h4 class="mb-0"><i class="bi bi-exclamation-triangle-fill me-2"></i>Error</h4> <h4 class="mb-0"><i class="bi bi-exclamation-triangle-fill me-2"></i>Template Rendering Error</h4>
</div> </div>
<div class="card-body"> <div class="card-body">
<h5 class="card-title">Something went wrong</h5> <h5 class="card-title">Something went wrong while rendering the template</h5>
<p class="card-text">{{ error }}</p>
<div class="alert alert-danger">
<p class="mb-2"><strong>Error Message:</strong></p>
<pre class="p-3 bg-light border rounded"><code>{{ error | default(value="Unknown error") }}</code></pre>
</div>
{% if error_details is defined and error_details %}
<div class="mt-3">
<p class="mb-2"><strong>Error Details:</strong></p>
<pre class="p-3 bg-light border rounded"><code>{{ error_details }}</code></pre>
</div>
{% endif %}
{% if error_location is defined and error_location %}
<div class="mt-3">
<p class="mb-2"><strong>Error Location:</strong></p>
<p>{{ error_location }}</p>
</div>
{% endif %}
<div class="alert alert-info mt-3">
<p class="mb-0"><i class="bi bi-info-circle me-2"></i>This error is visible only in development mode. In production, a generic error page will be shown.</p>
</div>
<div class="mt-4"> <div class="mt-4">
<a href="/" class="btn btn-primary me-2"> <a href="/" class="btn btn-primary me-2">
<i class="bi bi-house-door me-1"></i>Go to Home <i class="bi bi-house-door me-1"></i>Go to Home
</a> </a>
<a href="javascript:history.back()" class="btn btn-outline-secondary"> <a href="javascript:history.back()" class="btn btn-outline-secondary me-2">
<i class="bi bi-arrow-left me-1"></i>Go Back <i class="bi bi-arrow-left me-1"></i>Go Back
</a> </a>
<button onclick="window.location.reload()" class="btn btn-outline-primary">
<i class="bi bi-arrow-clockwise me-1"></i>Reload Page
</button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,14 +1,14 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}All Flows{% endblock %} {% block title %}Freezone Workflows{% endblock %}
{% block content %} {% block content %}
<div class="row mb-4"> <div class="row mb-4">
<div class="col-12"> <div class="col-12">
<nav aria-label="breadcrumb"> <nav aria-label="breadcrumb">
<ol class="breadcrumb"> <ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/flows">Flows</a></li> <li class="breadcrumb-item"><a href="/flows">Workflows</a></li>
<li class="breadcrumb-item active" aria-current="page">All Flows</li> <li class="breadcrumb-item active" aria-current="page">Freezone Workflows</li>
</ol> </ol>
</nav> </nav>
</div> </div>
@ -16,11 +16,11 @@
<div class="row mb-4"> <div class="row mb-4">
<div class="col-md-8"> <div class="col-md-8">
<h1 class="display-5 mb-0">All Flows</h1> <h1 class="display-5 mb-0">Freezone Workflow Management</h1>
</div> </div>
<div class="col-md-4 text-md-end"> <div class="col-md-4 text-md-end">
<a href="/flows/create" class="btn btn-primary"> <a href="/flows/create" class="btn btn-primary">
<i class="bi bi-plus-circle me-1"></i> Create New Flow <i class="bi bi-plus-circle me-1"></i> Create New Workflow
</a> </a>
</div> </div>
</div> </div>
@ -31,7 +31,7 @@
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<form class="row g-3" action="/flows/list" method="get"> <form class="row g-3" action="/flows/list" method="get">
<div class="col-md-4"> <div class="col-md-3">
<label for="status" class="form-label">Status</label> <label for="status" class="form-label">Status</label>
<select class="form-select" id="status" name="status"> <select class="form-select" id="status" name="status">
<option value="all" selected>All</option> <option value="all" selected>All</option>
@ -41,19 +41,34 @@
<option value="cancelled">Cancelled</option> <option value="cancelled">Cancelled</option>
</select> </select>
</div> </div>
<div class="col-md-4"> <!-- Freezone filter - for UI demonstration only -->
<label for="type" class="form-label">Type</label> <div class="col-md-3">
<label for="freezone" class="form-label">Freezone</label>
<select class="form-select" id="freezone" name="freezone" disabled>
<option value="all" selected>All Freezones</option>
<option value="dubai_multi_commodities_centre">DMCC</option>
<option value="dubai_international_financial_centre">DIFC</option>
<option value="jebel_ali_free_zone">JAFZA</option>
<option value="dubai_silicon_oasis">DSO</option>
<option value="dubai_internet_city">DIC</option>
<option value="dubai_media_city">DMC</option>
<option value="abu_dhabi_global_market">ADGM</option>
</select>
<div class="form-text">Coming soon</div>
</div>
<div class="col-md-3">
<label for="type" class="form-label">Workflow Type</label>
<select class="form-select" id="type" name="type"> <select class="form-select" id="type" name="type">
<option value="all" selected>All</option> <option value="all" selected>All</option>
<option value="company_registration">Company Registration</option> <option value="company_registration">Company Incorporation</option>
<option value="user_onboarding">User Onboarding</option> <option value="user_onboarding">KYC Verification</option>
<option value="service_activation">Service Activation</option> <option value="service_activation">License Activation</option>
<option value="payment_processing">Payment Processing</option> <option value="payment_processing">Payment Processing</option>
</select> </select>
</div> </div>
<div class="col-md-4"> <div class="col-md-3">
<label for="search" class="form-label">Search</label> <label for="search" class="form-label">Search</label>
<input type="text" class="form-control" id="search" name="search" placeholder="Search flows..."> <input type="text" class="form-control" id="search" name="search" placeholder="Search workflows...">
</div> </div>
<div class="col-12 text-end"> <div class="col-12 text-end">
<button type="submit" class="btn btn-primary"> <button type="submit" class="btn btn-primary">
@ -79,14 +94,14 @@
<table class="table table-hover"> <table class="table table-hover">
<thead> <thead>
<tr> <tr>
<th>Flow Name</th> <th>Workflow Name</th>
<th>Type</th> <th>Type</th>
<th>Status</th> <th>Status</th>
<th>Owner</th> <th>Assignee</th>
<th>Progress</th> <th>Progress</th>
<th>Created</th> <th>Initiated</th>
<th>Updated</th> <th>Last Updated</th>
<th>Current Step</th> <th>Current Stage</th>
<th>Actions</th> <th>Actions</th>
</tr> </tr>
</thead> </thead>
@ -120,21 +135,21 @@
{{ current.name }} {{ current.name }}
{% else %} {% else %}
{% if flow.status == 'Completed' %} {% if flow.status == 'Completed' %}
All steps completed <span class="text-success">All stages completed</span>
{% elif flow.status == 'Cancelled' %} {% elif flow.status == 'Cancelled' %}
Flow cancelled <span class="text-secondary">Workflow cancelled</span>
{% else %} {% else %}
No active step <span class="text-muted">No active stage</span>
{% endif %} {% endif %}
{% endif %} {% endif %}
</td> </td>
<td> <td>
<div class="btn-group"> <div class="btn-group">
<a href="/flows/{{ flow.id }}" class="btn btn-sm btn-primary"> <a href="/flows/{{ flow.id }}" class="btn btn-sm btn-primary" title="View Details">
<i class="bi bi-eye"></i> <i class="bi bi-eye"></i>
</a> </a>
{% if flow.status == 'In Progress' %} {% if flow.status == 'In Progress' %}
<a href="/flows/{{ flow.id }}#advance" class="btn btn-sm btn-success"> <a href="/flows/{{ flow.id }}#advance" class="btn btn-sm btn-success" title="Advance to Next Stage">
<i class="bi bi-arrow-right"></i> <i class="bi bi-arrow-right"></i>
</a> </a>
{% endif %} {% endif %}
@ -146,7 +161,11 @@
</table> </table>
</div> </div>
{% else %} {% else %}
<p class="text-center">No flows found matching your criteria.</p> <div class="text-center py-4">
<i class="bi bi-search display-1 text-muted"></i>
<p class="lead mt-3">No workflows found matching your criteria.</p>
<p class="text-muted">Try adjusting your filters or create a new workflow.</p>
</div>
{% endif %} {% endif %}
</div> </div>
</div> </div>