Rustelo/info/usage_example.md

485 lines
13 KiB
Markdown
Raw Normal View History

# Rustelo Template System Usage Example
This guide shows how to integrate and use the Rustelo template system in your application.
## Basic Integration
### 1. Update your main.rs
```rust
// Add to your main.rs
#[cfg(feature = "content-db")]
mod template;
use template::{TemplateService, TemplateConfig};
// In your main function or app initialization
async fn initialize_app() -> Result<(), Box<dyn std::error::Error>> {
// Initialize template service
let template_service = TemplateService::new("templates", "content/docs")?
.with_languages(vec!["en".to_string(), "es".to_string(), "fr".to_string()])
.with_default_language("en")
.with_cache(true);
// Add to your app state
let app_state = AppState {
template_service: Arc::new(template_service),
// ... other state
};
// Create router with template routes
let app = Router::new()
.merge(template::create_template_routes(app_state.template_service.clone()))
.with_state(app_state);
// Start server
let listener = tokio::net::TcpListener::bind("127.0.0.1:3030").await?;
axum::serve(listener, app).await?;
Ok(())
}
```
### 2. Update your AppState
```rust
#[derive(Clone)]
pub struct AppState {
pub leptos_options: LeptosOptions,
#[cfg(feature = "content-db")]
pub template_service: Arc<TemplateService>,
// ... other fields
}
```
### 3. Update Cargo.toml
Make sure `tera` is included in your `content-db` feature:
```toml
[features]
content-db = [
"sqlx",
"pulldown-cmark",
"syntect",
"serde_yaml",
"tempfile",
"uuid",
"chrono",
"tera" # Add this line
]
```
## Creating Your First Template Page
### Step 1: Create a Template
Create `templates/product-page.html`:
```html
<!DOCTYPE html>
<html lang="{{lang | default(value='en')}}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{title}} - {{site_name | default(value="My Store")}}</title>
<meta name="description" content="{{description}}">
<style>
body {
font-family: Arial, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
line-height: 1.6;
}
.product-header {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 40px;
margin-bottom: 40px;
}
.product-image {
width: 100%;
max-width: 500px;
height: auto;
border-radius: 8px;
}
.product-info h1 {
color: #333;
margin-bottom: 10px;
}
.price {
font-size: 1.5em;
color: #e74c3c;
font-weight: bold;
margin: 15px 0;
}
.description {
color: #666;
margin-bottom: 30px;
}
.buy-button {
background: #27ae60;
color: white;
padding: 15px 30px;
border: none;
border-radius: 5px;
font-size: 1.1em;
cursor: pointer;
text-decoration: none;
display: inline-block;
}
.buy-button:hover {
background: #219a52;
}
.features {
margin-top: 40px;
}
.features ul {
list-style-type: none;
padding: 0;
}
.features li {
padding: 10px 0;
border-bottom: 1px solid #eee;
}
.features li:before {
content: "✓";
color: #27ae60;
margin-right: 10px;
}
@media (max-width: 768px) {
.product-header {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="product-header">
<div class="product-image-container">
<img src="{{image_url}}" alt="{{title}}" class="product-image">
</div>
<div class="product-info">
<h1>{{title}}</h1>
<p class="description">{{description}}</p>
<div class="price">{{currency}}{{price}}</div>
{% if available %}
<a href="{{buy_url}}" class="buy-button">{{buy_button_text | default(value="Buy Now")}}</a>
{% else %}
<button disabled class="buy-button" style="background: #95a5a6;">{{out_of_stock_text | default(value="Out of Stock")}}</button>
{% endif %}
</div>
</div>
{% if features %}
<div class="features">
<h2>{{features_title | default(value="Features")}}</h2>
<ul>
{% for feature in features %}
<li>{{feature}}</li>
{% endfor %}
</ul>
</div>
{% endif %}
{% if reviews %}
<div class="reviews">
<h2>{{reviews_title | default(value="Customer Reviews")}}</h2>
{% for review in reviews %}
<div class="review" style="border: 1px solid #ddd; padding: 15px; margin: 10px 0; border-radius: 5px;">
<strong>{{review.author}}</strong>
<span style="color: #f39c12;">{{review.rating}}/5 ⭐</span>
<p>{{review.comment}}</p>
</div>
{% endfor %}
</div>
{% endif %}
</body>
</html>
```
### Step 2: Create English Configuration
Create `content/docs/en_awesome-widget.tpl.toml`:
```toml
template_name = "product-page"
[values]
title = "Awesome Widget Pro"
description = "The ultimate widget for all your needs. Built with premium materials and cutting-edge technology."
price = "99.99"
currency = "$"
image_url = "/images/awesome-widget.jpg"
buy_url = "/checkout/awesome-widget"
available = true
buy_button_text = "Buy Now"
out_of_stock_text = "Out of Stock"
features_title = "Key Features"
reviews_title = "Customer Reviews"
lang = "en"
site_name = "Widget Store"
features = [
"Premium aluminum construction",
"Wireless connectivity",
"5-year warranty",
"Easy setup in under 5 minutes",
"Compatible with all major platforms"
]
[[values.reviews]]
author = "John Smith"
rating = 5
comment = "Amazing product! Works exactly as advertised."
[[values.reviews]]
author = "Sarah Johnson"
rating = 4
comment = "Great quality, fast shipping. Highly recommend."
[[values.reviews]]
author = "Mike Chen"
rating = 5
comment = "Best widget I've ever used. Worth every penny."
[metadata]
category = "products"
product_id = "awesome-widget-pro"
sku = "AWP-001"
```
### Step 3: Create Spanish Configuration
Create `content/docs/es_awesome-widget.tpl.toml`:
```toml
template_name = "product-page"
[values]
title = "Widget Increíble Pro"
description = "El widget definitivo para todas sus necesidades. Construido con materiales premium y tecnología de vanguardia."
price = "99.99"
currency = "$"
image_url = "/images/awesome-widget.jpg"
buy_url = "/checkout/awesome-widget"
available = true
buy_button_text = "Comprar Ahora"
out_of_stock_text = "Agotado"
features_title = "Características Principales"
reviews_title = "Reseñas de Clientes"
lang = "es"
site_name = "Tienda de Widgets"
features = [
"Construcción premium de aluminio",
"Conectividad inalámbrica",
"Garantía de 5 años",
"Configuración fácil en menos de 5 minutos",
"Compatible con las principales plataformas"
]
[[values.reviews]]
author = "Juan Pérez"
rating = 5
comment = "¡Producto increíble! Funciona exactamente como se anuncia."
[[values.reviews]]
author = "María González"
rating = 4
comment = "Excelente calidad, envío rápido. Muy recomendado."
[[values.reviews]]
author = "Carlos Rodríguez"
rating = 5
comment = "El mejor widget que he usado. Vale cada centavo."
[metadata]
category = "productos"
product_id = "awesome-widget-pro"
sku = "AWP-001"
```
### Step 4: Access Your Pages
Now you can access your product pages:
- English: `http://localhost:3030/page:awesome-widget?lang=en`
- Spanish: `http://localhost:3030/page:awesome-widget?lang=es`
- Default: `http://localhost:3030/page:awesome-widget`
## Advanced Usage Examples
### Custom Route Handler
Create a custom handler that uses the template service:
```rust
use crate::template::TemplateService;
use axum::{extract::{Path, Query, State}, response::Html, http::StatusCode};
use serde::Deserialize;
#[derive(Deserialize)]
struct ProductQuery {
lang: Option<String>,
variant: Option<String>,
}
async fn product_page_handler(
Path(product_id): Path<String>,
Query(query): Query<ProductQuery>,
State(template_service): State<Arc<TemplateService>>,
) -> Result<Html<String>, StatusCode> {
let lang = query.lang.unwrap_or_else(|| "en".to_string());
// You could modify the content based on variant
let content_name = if let Some(variant) = query.variant {
format!("{}-{}", product_id, variant)
} else {
product_id
};
match template_service.render_page(&content_name, &lang).await {
Ok(rendered) => Ok(Html(rendered.content)),
Err(_) => Err(StatusCode::NOT_FOUND),
}
}
// Add to your router
let app = Router::new()
.route("/product/:id", get(product_page_handler))
.with_state(app_state);
```
### Dynamic Content Injection
```rust
use std::collections::HashMap;
use serde_json::Value;
async fn dynamic_page_handler(
Path(page_name): Path<String>,
State(template_service): State<Arc<TemplateService>>,
) -> Result<Html<String>, StatusCode> {
// Load base configuration
let mut config = template_service
.get_page_config(&page_name, "en")
.await
.map_err(|_| StatusCode::NOT_FOUND)?;
// Add dynamic content
config.values.insert(
"current_time".to_string(),
Value::String(chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string())
);
config.values.insert(
"visitor_count".to_string(),
Value::Number(get_visitor_count().into()) // Your function
);
// Render with custom context
let context = template_service.create_context(&config.values);
match template_service.render_with_context(&config.template_name, &context).await {
Ok(html) => Ok(Html(html)),
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
}
}
```
### API Integration
```rust
// Get all products for a language
async fn api_products_list(
Path(lang): Path<String>,
State(template_service): State<Arc<TemplateService>>,
) -> Result<Json<Vec<ProductSummary>>, StatusCode> {
let content_list = template_service
.get_available_content(&lang)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let mut products = Vec::new();
for content_name in content_list {
if let Ok(config) = template_service.get_page_config(&content_name, &lang).await {
if let Some(category) = config.metadata.and_then(|m| m.get("category")) {
if category == "products" {
products.push(ProductSummary {
id: content_name,
title: config.values.get("title").unwrap_or(&Value::String("".to_string())).as_str().unwrap_or("").to_string(),
price: config.values.get("price").unwrap_or(&Value::String("0".to_string())).as_str().unwrap_or("0").to_string(),
});
}
}
}
}
Ok(Json(products))
}
#[derive(Serialize)]
struct ProductSummary {
id: String,
title: String,
price: String,
}
```
## Development Tips
### 1. Hot Reload During Development
Add `?reload=true` to your URLs during development:
```
http://localhost:3030/page:awesome-widget?lang=en&reload=true
```
### 2. Template Debugging
Use the API endpoints to debug:
```bash
# Check if template exists
curl http://localhost:3030/api/template/exists/awesome-widget?lang=en
# Get template configuration
curl http://localhost:3030/api/template/config/awesome-widget?lang=en
# Get template service stats
curl http://localhost:3030/api/template/stats
```
### 3. Performance Monitoring
```rust
// Log template rendering time
let start = std::time::Instant::now();
let result = template_service.render_page("awesome-widget", "en").await;
let duration = start.elapsed();
tracing::info!("Template rendered in {:?}", duration);
```
### 4. Error Handling
```rust
async fn safe_template_render(
template_service: &TemplateService,
content_name: &str,
lang: &str,
) -> Html<String> {
match template_service.render_page(content_name, lang).await {
Ok(rendered) => Html(rendered.content),
Err(e) => {
tracing::error!("Template render failed: {}", e);
Html(format!(
r#"<html><body><h1>Page Not Found</h1><p>The requested page "{}" is not available.</p></body></html>"#,
content_name
))
}
}
}
```
This template system provides a flexible, performant way to create localized content in your Rustelo application. The combination of Tera templates and TOML configuration files makes it easy to manage content while maintaining the performance benefits of Rust.