Skip to main content

backbone_payroll/presentation/http/
payroll_entry_handler.rs

1//! PayrollEntry REST handlers
2//!
3//! Generated by metaphor-schema. Do not edit manually.
4//!
5//! Uses Axum and backbone-core's BackboneCrudHandler for all 12 CRUD endpoints.
6
7use std::collections::HashMap;
8use std::sync::Arc;
9
10use axum::Router;
11use serde::{Deserialize, Serialize};
12use uuid::Uuid;
13use chrono::{DateTime, Utc, NaiveDate};
14use rust_decimal::Decimal;
15
16// Backbone framework imports
17use backbone_core::http::BackboneCrudHandler;
18
19// Auth integration (optional)
20#[cfg(feature = "auth")]
21use backbone_auth::middleware::AuthContext;
22#[cfg(feature = "auth")]
23use backbone_auth::AuthMiddleware;
24
25// Domain imports
26use crate::domain::entity::*;
27use crate::application::service::{PayrollEntryService, ServiceError};
28
29// DTO imports
30use crate::presentation::dto::{CreatePayrollEntryDto, UpdatePayrollEntryDto, PatchPayrollEntryDto, PayrollEntryResponseDto};
31
32
33/// Application error type
34#[derive(Debug, thiserror::Error)]
35pub enum PayrollEntryError {
36    #[error("Not found: {0}")]
37    NotFound(String),
38    #[error("Validation error: {0}")]
39    Validation(String),
40    #[error("Database error: {0}")]
41    Database(String),
42    #[error("Internal error: {0}")]
43    Internal(String),
44}
45
46impl From<ServiceError> for PayrollEntryError {
47    fn from(err: ServiceError) -> Self {
48        match err {
49            ServiceError::NotFound => Self::NotFound(err.to_string()),
50            ServiceError::Validation(ref msg) => Self::Validation(msg.clone()),
51            ServiceError::AlreadyExists(ref msg) => Self::Validation(msg.clone()),
52            ServiceError::Repository(ref e) => Self::Database(e.to_string()),
53            ServiceError::Internal(ref msg) => Self::Internal(msg.clone()),
54        }
55    }
56}
57
58impl axum::response::IntoResponse for PayrollEntryError {
59    fn into_response(self) -> axum::response::Response {
60        use axum::http::StatusCode;
61        use axum::Json;
62
63        let (status, code) = match &self {
64            Self::NotFound(_) => (StatusCode::NOT_FOUND, "PAYROLLENTRY_NOT_FOUND"),
65            Self::Validation(_) => (StatusCode::BAD_REQUEST, "PAYROLLENTRY_VALIDATION_ERROR"),
66            Self::Database(_) => (StatusCode::INTERNAL_SERVER_ERROR, "PAYROLLENTRY_DATABASE_ERROR"),
67            Self::Internal(_) => (StatusCode::INTERNAL_SERVER_ERROR, "PAYROLLENTRY_INTERNAL_ERROR"),
68        };
69
70        let body = serde_json::json!({
71            "success": false,
72            "error": code,
73            "message": self.to_string(),
74        });
75
76        (status, Json(body)).into_response()
77    }
78}
79
80// =============================================================================
81// Route Configuration
82// =============================================================================
83
84/// Create Axum router with all 16 Backbone endpoints for PayrollEntry.
85///
86/// # Routes
87///
88/// | Method | Path | Description |
89/// |--------|------|-------------|
90/// | GET | /payroll_entries | List with pagination |
91/// | POST | /payroll_entries | Create new |
92/// | GET | /payroll_entries/:id | Get by ID |
93/// | PUT | /payroll_entries/:id | Full update |
94/// | PATCH | /payroll_entries/:id | Partial update |
95/// | DELETE | /payroll_entries/:id | Soft delete |
96/// | POST | /payroll_entries/bulk | Bulk create |
97/// | POST | /payroll_entries/upsert | Upsert |
98/// | GET | /payroll_entries/trash | List deleted |
99/// | POST | /payroll_entries/:id/restore | Restore |
100/// | DELETE | /payroll_entries/empty | Empty trash |
101/// | GET | /payroll_entries/:id/deleted | Get deleted by ID |
102/// | DELETE | /payroll_entries/trash/:id | Permanent delete from trash |
103/// | GET | /payroll_entries/count | Count active entities |
104/// | GET | /payroll_entries/trash/count | Count deleted entities |
105///
106/// # Example
107///
108/// ```text
109/// let service = Arc::new(PayrollEntryService::with_repository(repository));
110/// let router = create_payroll_entry_routes(service);
111/// ```
112pub fn create_payroll_entry_routes(service: Arc<PayrollEntryService>) -> Router {
113    BackboneCrudHandler::<PayrollEntryService, PayrollEntry, CreatePayrollEntryDto, UpdatePayrollEntryDto, PayrollEntryResponseDto>::routes(
114        service,
115        "/payroll_entries",
116    )
117}
118
119/// Create Axum router with only the read (GET) endpoints for PayrollEntry.
120///
121/// Safe for public, unauthenticated exposure (e.g., reference data).
122/// Mutations must be served separately via `create_payroll_entry_write_routes`,
123/// typically wrapped in an auth middleware layer.
124pub fn create_payroll_entry_read_routes(service: Arc<PayrollEntryService>) -> Router {
125    BackboneCrudHandler::<PayrollEntryService, PayrollEntry, CreatePayrollEntryDto, UpdatePayrollEntryDto, PayrollEntryResponseDto>::read_routes(
126        service,
127        "/payroll_entries",
128    )
129}
130
131/// Create Axum router with only the write (mutation) endpoints for PayrollEntry.
132///
133/// These routes must NOT be publicly exposed. Wrap them with an auth
134/// middleware before nesting into the application router.
135///
136/// # This is unguarded generic CRUD, not a validated write path
137///
138/// These are plain create/update/patch/delete mutations over the entity row —
139/// they bypass all business invariants. If the module exposes a validated write
140/// service (e.g. a command router over its domain engine), serve THAT instead
141/// for any mutation that must respect domain rules.
142pub fn create_payroll_entry_write_routes(service: Arc<PayrollEntryService>) -> Router {
143    BackboneCrudHandler::<PayrollEntryService, PayrollEntry, CreatePayrollEntryDto, UpdatePayrollEntryDto, PayrollEntryResponseDto>::write_routes(
144        service,
145        "/payroll_entries",
146    )
147}
148
149/// Create authenticated routes with auth middleware.
150///
151/// Requires the `auth` feature flag. The `AuthMiddleware` implementation
152/// is responsible for extracting and validating tokens, then providing
153/// an `AuthContext` via request extensions.
154#[cfg(feature = "auth")]
155pub fn create_protected_payroll_entry_routes<A: AuthMiddleware + Send + Sync + 'static>(
156    service: Arc<PayrollEntryService>,
157    auth: Arc<A>,
158) -> Router {
159    use axum::middleware;
160    use axum::response::IntoResponse;
161
162    let auth_layer = auth.clone();
163    create_payroll_entry_routes(service)
164        .layer(middleware::from_fn(move |mut req: axum::extract::Request, next: axum::middleware::Next| {
165            let auth = auth_layer.clone();
166            async move {
167                let token = req.headers()
168                    .get(axum::http::header::AUTHORIZATION)
169                    .and_then(|h| h.to_str().ok())
170                    .and_then(|raw| raw.strip_prefix("Bearer ").or_else(|| raw.strip_prefix("bearer ")))
171                    .unwrap_or("");
172                match auth.authenticate(token).await {
173                    Ok(ctx) => {
174                        req.extensions_mut().insert(ctx);
175                        next.run(req).await
176                    }
177                    Err(_) => {
178                        (axum::http::StatusCode::UNAUTHORIZED,
179                         axum::Json(serde_json::json!({
180                             "success": false,
181                             "error": "unauthorized",
182                             "message": "Authentication required"
183                         }))
184                        ).into_response()
185                    }
186                }
187            }
188        }))
189}