Skip to main content

admin_demo/
admin_demo.rs

1//! Admin engine demo. Boots the framework with the new admin engine
2//! mounted at `/admin`. Two demo models are registered automatically
3//! by the framework — `users` (a hand-written `AdminUiModel`) and
4//! `orders` (a `register_generated` config-driven model). This demo
5//! seeds both tables with sample rows so the lists are not empty on
6//! first load.
7//!
8//! Run:
9//!   cargo run --example admin_demo -p rustio-core
10//!
11//! Open http://127.0.0.1:3000/admin and sign in as
12//! admin@example.com / admin.
13
14use std::net::SocketAddr;
15
16use rustio_core::admin::Admin;
17use rustio_core::auth::{self, authenticate};
18use rustio_core::defaults::with_defaults;
19use rustio_core::{Db, Router, Server};
20
21#[tokio::main]
22async fn main() -> std::io::Result<()> {
23    let db = Db::memory().await.expect("db connect");
24    auth::ensure_core_tables(&db)
25        .await
26        .expect("create auth tables");
27
28    auth::user::create(&db, "admin@example.com", "admin", "admin")
29        .await
30        .expect("seed admin user");
31
32    seed_demo_data(&db).await;
33
34    let router = with_defaults(Router::new()).wrap(authenticate(db.clone()));
35    // No `.model::<T>()` calls: the new engine uses its own
36    // `AdminUiModel` registry (Users + Orders are wired in by
37    // `Admin::register`). The legacy AdminModel + macro path is
38    // intentionally not exercised here so there is exactly one
39    // admin surface in the demo.
40    let router = Admin::new().register(router, &db);
41
42    let addr: SocketAddr = ([127, 0, 0, 1], 3000).into();
43    eprintln!("admin demo: open http://{addr}/admin and sign in as admin@example.com / admin");
44    Server::bind(addr).serve_router(router).await
45}
46
47/// Insert sample rows into the demo tables so the admin index isn't
48/// empty on first boot. Tables are auto-created by each model's
49/// `ensure_table_sql()` on first request, so we run them here too
50/// to make seeding deterministic regardless of which page is hit
51/// first.
52///
53/// Uses `Db::execute` with literal SQL because the framework keeps
54/// sqlx hidden from user code (no `pool()` access from outside the
55/// crate). All seed values are compile-time string literals — no
56/// untrusted input — so literal interpolation is safe here.
57async fn seed_demo_data(db: &Db) {
58    db.execute(
59        "CREATE TABLE IF NOT EXISTS admin_new_demo_users (
60            id INTEGER PRIMARY KEY AUTOINCREMENT,
61            username TEXT NOT NULL,
62            email TEXT NOT NULL,
63            is_active TEXT NOT NULL DEFAULT 'false',
64            doctor_id TEXT,
65            salary_amount TEXT
66        )",
67    )
68    .await
69    .expect("create users table");
70
71    db.execute(
72        "CREATE TABLE IF NOT EXISTS admin_new_demo_orders (
73            id INTEGER PRIMARY KEY AUTOINCREMENT,
74            order_number TEXT,
75            customer_email TEXT,
76            total_amount TEXT,
77            is_paid TEXT
78        )",
79    )
80    .await
81    .expect("create orders table");
82
83    let user_inserts = "INSERT INTO admin_new_demo_users \
84         (username, email, is_active, doctor_id, salary_amount) VALUES \
85         ('amansour',  'abdulwahed@rustio.dev', 'true',  '1', '5400'), \
86         ('b.hassan',  'bashayer@rustio.dev',   'true',  '1', '4800'), \
87         ('sara_m',    'sara@rustio.dev',       'true',  '2', '3600'), \
88         ('l.nguyen',  'linh@example.com',      'true',  '1', '5100'), \
89         ('k.ito',     'kenji@example.com',     'true',  '2', '3200'), \
90         ('m.osei',    'maya@example.com',      'true',  '2', '3000'), \
91         ('r.silva',   'rafael@example.com',    'false', '1', '2800'), \
92         ('p.kapoor',  'priya@example.com',     'true',  '2', '4200'), \
93         ('n.eriksson','nils@example.com',      'true',  '1', '3900'), \
94         ('z.ahmed',   'zainab@example.com',    'false', '2', '2100')";
95    let _ = db.execute(user_inserts).await;
96
97    let order_inserts = "INSERT INTO admin_new_demo_orders \
98         (order_number, customer_email, total_amount, is_paid) VALUES \
99         ('RIO-1001', 'abdulwahed@rustio.dev', '129.00', 'true'), \
100         ('RIO-1002', 'bashayer@rustio.dev',   '89.50',  'true'), \
101         ('RIO-1003', 'sara@rustio.dev',       '245.00', 'false'), \
102         ('RIO-1004', 'linh@example.com',      '320.00', 'true'), \
103         ('RIO-1005', 'kenji@example.com',     '57.25',  'false'), \
104         ('RIO-1006', 'maya@example.com',      '412.10', 'true'), \
105         ('RIO-1007', 'rafael@example.com',    '76.40',  'false'), \
106         ('RIO-1008', 'priya@example.com',     '199.99', 'true'), \
107         ('RIO-1009', 'nils@example.com',      '88.00',  'true'), \
108         ('RIO-1010', 'zainab@example.com',    '33.00',  'false')";
109    let _ = db.execute(order_inserts).await;
110}