Skip to content

المسارات

المسارات (Routes) تُعيّن طلبات HTTP الواردة لدوال المعالجات. يُنظّم FORGE المسارات في مجموعتين: مسارات admin لنقاط نهاية الإدارة المُصادق عليها ومسارات عامة للبيانات المُقدّمة للعملاء. تُسجّل المسارات باستخدام Router في Axum وتُركّب مع middleware للمصادقة والتفويض.

تنظيم المسارات

/api/
├── auth/
│   ├── POST   login
│   ├── POST   register
│   ├── POST   refresh
│   ├── POST   logout
│   ├── POST   forgot-password
│   ├── POST   reset-password
│   ├── POST   send-otp
│   └── POST   verify-otp
├── profile              GET, PUT (مُصادق)
├── contents/{slug}      GET (عام)
├── lookups/{type}       GET (عام)
├── menus/{location}     GET (عام)
└── admin/               (مصادقة + صلاحيات مطلوبة)
    ├── users/           GET, POST
    │   └── {id}         GET, PUT, DELETE
    │       └── roles    POST
    ├── roles/           GET, POST
    │   └── {id}         GET, PUT, DELETE
    ├── permissions/     GET
    ├── contents/        GET, POST
    │   └── {id}         GET, PUT, DELETE
    ├── lookups/         GET, POST
    │   └── {id}         GET, PUT, DELETE
    ├── menus/           GET, POST
    │   └── {id}         GET, PUT, DELETE
    └── settings/        GET, PUT

تسجيل المسارات

الـ Router الرئيسي

الـ router الأعلى مستوى في main.rs يُركّب جميع مجموعات المسارات:

rust
// src/main.rs
let app = Router::new()
    // Admin routes (auth and RBAC middleware applied inside)
    .nest("/api/admin", routes::admin::routes(pool.clone()))
    // Public API routes
    .nest("/api", routes::public::routes(pool.clone()))
    // Health checks
    .route("/health", get(|| async { "OK" }))
    .route("/ready", get(health_check))
    // Documentation
    .merge(SwaggerUi::new("/docs").url("/api-docs/openapi.json", ApiDoc::openapi()))
    .merge(Redoc::with_url("/redoc", ApiDoc::openapi()))
    // Global middleware
    .layer(CorsLayer::permissive())
    .layer(middleware::from_fn(middleware::request_id::add_request_id));

مسارات Admin

مسارات Admin تُطبّق middleware المصادقة على المجموعة بأكملها وmiddleware الصلاحيات على المسارات الفردية:

rust
// src/routes/admin.rs
use axum::{Router, routing::{get, post, put, delete}, middleware};
use crate::handlers::admin;
use crate::middleware::{auth::auth_middleware, rbac::require_permission};

pub fn routes(pool: PgPool) -> Router {
    Router::new()
        // User management
        .route(
            "/users",
            get(admin::users::list)
                .route_layer(middleware::from_fn_with_state(
                    pool.clone(),
                    require_permission("users.view"),
                ))
        )
        .route(
            "/users",
            post(admin::users::create)
                .route_layer(middleware::from_fn_with_state(
                    pool.clone(),
                    require_permission("users.create"),
                ))
        )
        .route(
            "/users/:id",
            get(admin::users::show)
                .route_layer(middleware::from_fn_with_state(
                    pool.clone(),
                    require_permission("users.view"),
                ))
        )
        .route(
            "/users/:id",
            put(admin::users::update)
                .route_layer(middleware::from_fn_with_state(
                    pool.clone(),
                    require_permission("users.edit"),
                ))
        )
        .route(
            "/users/:id",
            delete(admin::users::delete)
                .route_layer(middleware::from_fn_with_state(
                    pool.clone(),
                    require_permission("users.delete"),
                ))
        )
        .route(
            "/users/:id/roles",
            post(admin::users::assign_roles)
                .route_layer(middleware::from_fn_with_state(
                    pool.clone(),
                    require_permission("roles.edit"),
                ))
        )
        // Role management
        .route(
            "/roles",
            get(admin::roles::list)
                .route_layer(middleware::from_fn_with_state(
                    pool.clone(),
                    require_permission("roles.view"),
                ))
        )
        .route(
            "/roles",
            post(admin::roles::create)
                .route_layer(middleware::from_fn_with_state(
                    pool.clone(),
                    require_permission("roles.create"),
                ))
        )
        .route(
            "/roles/:id",
            put(admin::roles::update)
                .route_layer(middleware::from_fn_with_state(
                    pool.clone(),
                    require_permission("roles.edit"),
                ))
        )
        .route(
            "/roles/:id",
            delete(admin::roles::delete)
                .route_layer(middleware::from_fn_with_state(
                    pool.clone(),
                    require_permission("roles.delete"),
                ))
        )
        // Permissions (read-only)
        .route(
            "/permissions",
            get(admin::permissions::list)
                .route_layer(middleware::from_fn_with_state(
                    pool.clone(),
                    require_permission("roles.view"),
                ))
        )
        // Content management
        .route(
            "/contents",
            get(admin::contents::list)
                .route_layer(middleware::from_fn_with_state(
                    pool.clone(),
                    require_permission("contents.view"),
                ))
        )
        .route(
            "/contents",
            post(admin::contents::create)
                .route_layer(middleware::from_fn_with_state(
                    pool.clone(),
                    require_permission("contents.create"),
                ))
        )
        .route(
            "/contents/:id",
            get(admin::contents::show)
                .route_layer(middleware::from_fn_with_state(
                    pool.clone(),
                    require_permission("contents.view"),
                ))
        )
        .route(
            "/contents/:id",
            put(admin::contents::update)
                .route_layer(middleware::from_fn_with_state(
                    pool.clone(),
                    require_permission("contents.edit"),
                ))
        )
        .route(
            "/contents/:id",
            delete(admin::contents::delete)
                .route_layer(middleware::from_fn_with_state(
                    pool.clone(),
                    require_permission("contents.delete"),
                ))
        )
        // Settings
        .route("/settings", get(admin::settings::list))
        .route("/settings", put(admin::settings::update))
        // Apply auth middleware to all admin routes
        .layer(middleware::from_fn_with_state(
            pool.clone(),
            auth_middleware,
        ))
        .with_state(pool)
}

تحذير

auth_middleware يُطبّق كـ layer على كامل router الـ admin. هذا يعني كل مسار تحت /api/admin/* يتطلب JWT token صالح. المسارات الفردية ثم تُطبّق require_permission للتحكم الدقيق بالوصول.

المسارات العامة

المسارات العامة متاحة بدون مصادقة، رغم أن بعضها قد يستخدمها اختيارياً:

rust
// src/routes/public.rs
use axum::{Router, routing::{get, post}};
use crate::handlers;

pub fn routes(pool: PgPool) -> Router {
    Router::new()
        // Authentication (no auth required)
        .route("/auth/login", post(handlers::auth::login))
        .route("/auth/register", post(handlers::auth::register))
        .route("/auth/refresh", post(handlers::auth::refresh))
        .route("/auth/logout", post(handlers::auth::logout))
        .route("/auth/forgot-password", post(handlers::auth::forgot_password))
        .route("/auth/reset-password", post(handlers::auth::reset_password))
        .route("/auth/send-otp", post(handlers::auth::send_otp))
        .route("/auth/verify-otp", post(handlers::auth::verify_otp))
        // Profile (auth required)
        .route(
            "/profile",
            get(handlers::profile::show)
                .put(handlers::profile::update)
                .route_layer(middleware::from_fn_with_state(
                    pool.clone(),
                    auth_middleware,
                ))
        )
        // Public content (no auth required)
        .route("/contents/:slug", get(handlers::contents::show))
        .route("/lookups/:type", get(handlers::lookups::by_type))
        .route("/menus/:location", get(handlers::menus::by_location))
        .with_state(pool)
}

تطبيق Middleware

يُطبّق Middleware على مستويات مختلفة حسب النطاق:

Middleware عام (جميع المسارات)
├── CORS
├── Request ID

├── /api/admin/* (مسارات admin)
│   ├── Auth Middleware (جميع مسارات admin)
│   │   ├── /users       → require_permission("users.view")
│   │   ├── /users POST  → require_permission("users.create")
│   │   └── ...
│   │
├── /api/* (مسارات عامة)
│   ├── /auth/*           → بدون middleware
│   ├── /profile          → Auth Middleware (مستوى المسار)
│   └── /contents/:slug   → بدون middleware

نصيحة

طبّق middleware على أضيق نطاق ممكن. Middleware العام يعمل على كل طلب، بما فيها فحوصات الصحة ونقاط نهاية التوثيق. استخدم route_layer لـ middleware خاص بالمسار لإبقاء الحمل الزائد أدنى ما يمكن.

أنماط المسارات

مسارات CRUD للموارد

النمط القياسي لمورد جديد:

rust
// Add to src/routes/admin.rs
.route("/products", get(admin::products::list))
.route("/products", post(admin::products::create))
.route("/products/:id", get(admin::products::show))
.route("/products/:id", put(admin::products::update))
.route("/products/:id", delete(admin::products::delete))

مسارات الموارد المتداخلة

للموارد التي تنتمي لأب:

rust
// Comments belong to a content entry
.route("/contents/:content_id/comments", get(admin::comments::list))
.route("/contents/:content_id/comments", post(admin::comments::create))
.route("/contents/:content_id/comments/:id", put(admin::comments::update))
.route("/contents/:content_id/comments/:id", delete(admin::comments::delete))

مسارات الإجراءات

للإجراءات غير CRUD على مورد:

rust
// Custom actions
.route("/users/:id/roles", post(admin::users::assign_roles))
.route("/users/:id/activate", post(admin::users::activate))
.route("/users/:id/deactivate", post(admin::users::deactivate))

معاملات المسار

يستخرج Axum معاملات المسار باستخدام مُستخرج Path:

rust
// Single parameter
.route("/users/:id", get(show_user))

async fn show_user(Path(id): Path<Uuid>) -> impl IntoResponse { /* ... */ }

// Multiple parameters
.route("/contents/:content_id/comments/:id", get(show_comment))

async fn show_comment(
    Path((content_id, id)): Path<(Uuid, Uuid)>,
) -> impl IntoResponse { /* ... */ }

انظر أيضاً

Released under the MIT License.