======================env===============
.env
DATABASE_URL=mysql://user:password@127.0.0.1:3306/motoshare
BIND_ADDR=127.0.0.1:8080
SESSION_KEY=change-this-in-prod
DATABASE_URL=mysql://root:@127.0.0.1:3306/rust_crud
APP_BIND=127.0.0.1:8080
APP_NAME=Rust CRUD Dashboard
=============cargo.toml============
Cargo Setup
[dependencies]
actix-files = "0.6.8"
actix-multipart = "0.7.2"
actix-cors = "0.7.1"
actix-session = { version = "0.10.1", features = ["cookie-session"] }
actix-web = "4.11.0"
chrono = { version = "0.4.40", features = ["serde"] }
dotenvy = "0.15.7"
futures-util = "0.3.31"
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
sqlx = { version = "0.8.3", features = ["runtime-tokio-rustls", "mysql", "chrono"] }
tera = "1.20.0"
tokio = { version = "1.44.1", features = ["fs", "io-util"] }
uuid = { version = "1.16.0", features = ["v4"] }
image = { version = "0.25.5", default-features = false, features = ["jpeg", "png", "webp", "gif"] }
Add Home DTO Models
C:\xampp\htdocs\rust_crud_app\src\models.rs
[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HomeVehicleImage {
pub file: String,
pub src: String, // "partner" | "default"
}
[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HomeVehicleCard {
pub id: u64,
pub vehical: String,
pub brand: String,
pub model: String,
pub city: String,
pub price: String,
pub dummy_data: String,
pub images: Vec,
}
DB Query (Fast + 9 rows + joins)
C:\xampp\htdocs\rust_crud_app\src\db\vehicle.rs
collections
use sqlx::MySqlPool;
use std::collections::HashSet;
use crate::models::{HomeVehicleCard, HomeVehicleImage, Vehicle, VehicleForm};
[derive(Debug, sqlx::FromRow)]
struct HomeVehicleRow {
id: u64,
vehical: Option,
brand: Option,
model: Option,
city: Option,
price: Option,
dummy_data: Option,
vechicle_image: Option,
}
fn push_images(
out: &mut Vec,
seen: &mut HashSet,
names: &[String],
src: &str,
max_count: usize,
) {
for name in names.iter().take(max_count) {
let clean = name.trim();
if clean.is_empty() || clean.eq_ignore_ascii_case("null") {
continue;
}
if !seen.insert(clean.to_string()) {
continue;
}
out.push(HomeVehicleImage {
file: clean.to_string(),
src: src.to_string(),
});
}
}
fn parse_images(raw: Option) -> Vec {
let txt = raw.unwrap_or_default();
let text = txt.trim();
if text.is_empty() {
return Vec::new();
}
let mut out = Vec::new();
let mut seen = HashSet::new();
if let Ok(v) = serde_json::from_str::<serde_json::Value>(text) {
if let Some(obj) = v.as_object() {
let partner = obj
.get("partner")
.and_then(|x| x.as_array())
.map(|arr| {
arr.iter()
.filter_map(|x| x.as_str().map(|s| s.to_string()))
.collect::<Vec<_>>()
})
.unwrap_or_default();
let admin = obj
.get("admin")
.and_then(|x| x.as_array())
.map(|arr| {
arr.iter()
.filter_map(|x| x.as_str().map(|s| s.to_string()))
.collect::<Vec<_>>()
})
.unwrap_or_default();
// 1 partner + 4 system images for home cards.
push_images(&mut out, &mut seen, &partner, "partner", 1);
push_images(&mut out, &mut seen, &admin, "default", 4);
return out;
}
}
// Legacy fallback: single filename stored directly.
let clean = text
.trim_matches('"')
.trim_matches('\'')
.trim()
.to_string();
if !clean.is_empty() && !clean.eq_ignore_ascii_case("null") {
out.push(HomeVehicleImage {
file: clean,
src: "default".to_string(),
});
}
out
}
pub async fn list_home_vehicle_cards(
pool: &MySqlPool,
limit: i64,
) -> Result, sqlx::Error> {
let rows = sqlx::query_as::<_, HomeVehicleRow>(
"SELECT
av.id AS id,
CAST(COALESCE(bm.vehical, av.vechicle) AS CHAR) AS vehical,
CAST(COALESCE(av.brand, bm.brand) AS CHAR) AS brand,
CAST(COALESCE(av.model, bm.model) AS CHAR) AS model,
CAST(COALESCE(s.city, av.city) AS CHAR) AS city,
CAST(COALESCE(av.price, '') AS CHAR) AS price,
CAST(COALESCE(av.dummy_data, '0') AS CHAR) AS dummy_data,
CAST(COALESCE(av.vechicle_image, '') AS CHAR) AS vechicle_image
FROM addvechicles av
LEFT JOIN addvehical_byadmins bm ON bm.id = av.vehical_id
LEFT JOIN shops s ON s.id = av.shop_id
LEFT JOIN users u ON u.id = av.vender_ID
WHERE av.status = 'active'
ORDER BY av.id DESC
LIMIT ?",
)
.bind(limit)
.fetch_all(pool)
.await?;
Ok(rows
.into_iter()
.map(|r| HomeVehicleCard {
id: r.id,
vehical: r.vehical.unwrap_or_default(),
brand: r.brand.unwrap_or_default(),
model: r.model.unwrap_or_default(),
city: r.city.unwrap_or_default(),
price: r.price.unwrap_or_default(),
dummy_data: r.dummy_data.unwrap_or_else(|| "0".to_string()),
images: parse_images(r.vechicle_image),
})
.collect())
}
============================
Export DB Function
C:\xampp\htdocs\rust_crud_app\src\db.rs
pub use vehicle::{
create_vehicle, delete_vehicle, get_vehicle_by_id, list_home_vehicle_cards, list_vehicles,
update_vehicle,
};
Add Handler
C:\xampp\htdocs\rust_crud_app\src\handlers\vehicle.rs
use crate::db::{
create_vehicle, delete_vehicle, get_vehicle_by_id, list_home_vehicle_cards, list_vehicles,
update_vehicle, AppState,
};
use crate::models::VehicleForm;
use super::common::{compress_uploaded_image, ensure_login_user_id, ApiResponse};
pub async fn list_home_vehicle_cards_handler(state: web::Data) -> Result {
let cards = list_home_vehicle_cards(&state.pool, 9)
.await
.map_err(error::ErrorInternalServerError)?;
let response = ApiResponse {
message: "Home vehicles fetched".to_string(),
data: cards,
};
Ok(HttpResponse::Ok().json(response))
}
Export Handler
C:\xampp\htdocs\rust_crud_app\src\handlers.rs
pub use vehicle::{
create_vehicle_handler,
delete_vehicle_handler,
get_vehicle_handler,
list_home_vehicle_cards_handler,
list_vehicles_handler,
update_vehicle_handler,
upload_vehicle_documents_handler,
upload_vehicle_partner_image_handler,
};
Add Route
// src/handlers.rs
pub use vehicle::list_home_vehicle_cards_handler;
Frontend API Method
// frontend/src/api.js
listHomeVehicles: () => request("/home/vehicles"),
Load in App
// frontend/src/App.jsx
const [homeVehicles, setHomeVehicles] = useState([]);
const loadHomeVehicles = async () => {
try {
const result = await api.listHomeVehicles();
setHomeVehicles(Array.isArray(result.data) ? result.data.slice(0, 9) : []);
} catch {
const fallback = await api.listVehicles();
setHomeVehicles(Array.isArray(fallback.data) ? fallback.data.slice(0, 9) : []);
}
};
// call on boot
await loadHomeVehicles();
// pass to Home
Render Home Section
// frontend/src/pages/HomePage.jsx
import HomeAfterIntroSection from "../components/HomeAfterIntroSection";
export default function HomePage({ vehicles = [], ...props }) {
return (
{/* hero/search */}
);
}
Slider Component (auto-scroll + partner/system badge)
// frontend/src/components/HomeAfterIntroSection.jsx
// - parse v.images (new API)
// - fallback parse v.vechicle_image (legacy)
// - auto rotate every 3s
// - show badge: "uploaded by Partner" / "System photo"
DB Indexes for Performance
CREATE INDEX idx_addvechicles_home ON addvechicles (status, id);
CREATE INDEX idx_addvechicles_fk1 ON addvechicles (vehical_id);
CREATE INDEX idx_addvechicles_fk2 ON addvechicles (shop_id);
CREATE INDEX idx_addvechicles_fk3 ON addvechicles (vender_ID);
CREATE INDEX idx_addvehical_byadmins_id ON addvehical_byadmins (id);
CREATE INDEX idx_shops_id ON shops (id);
CREATE INDEX idx_users_id ON users (id);
============
cargo run
frontend
npm run dev
Top comments (0)