Skip to main content

backbone_bucket/presentation/http/
content_hash_handler.rs

1//! ContentHash 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};
14
15// Backbone framework imports
16use backbone_core::http::BackboneCrudHandler;
17
18// Auth integration (optional)
19#[cfg(feature = "auth")]
20use backbone_auth::middleware::AuthContext;
21#[cfg(feature = "auth")]
22use backbone_auth::AuthMiddleware;
23
24// Domain imports
25use crate::domain::entity::*;
26use crate::application::service::{ContentHashService, ServiceError};
27
28// DTO imports
29use crate::presentation::dto::{CreateContentHashDto, UpdateContentHashDto, PatchContentHashDto, ContentHashResponseDto};
30
31
32/// Application error type
33#[derive(Debug, thiserror::Error)]
34pub enum ContentHashError {
35    #[error("Not found: {0}")]
36    NotFound(String),
37    #[error("Validation error: {0}")]
38    Validation(String),
39    #[error("Database error: {0}")]
40    Database(String),
41    #[error("Internal error: {0}")]
42    Internal(String),
43}
44
45impl From<ServiceError> for ContentHashError {
46    fn from(err: ServiceError) -> Self {
47        match err {
48            ServiceError::NotFound => Self::NotFound(err.to_string()),
49            ServiceError::Validation(ref msg) => Self::Validation(msg.clone()),
50            ServiceError::AlreadyExists(ref msg) => Self::Validation(msg.clone()),
51            ServiceError::Repository(ref e) => Self::Database(e.to_string()),
52            ServiceError::Internal(ref msg) => Self::Internal(msg.clone()),
53            ServiceError::Violations(_) => Self::Validation(err.to_string()),
54        }
55    }
56}
57
58impl axum::response::IntoResponse for ContentHashError {
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, "CONTENTHASH_NOT_FOUND"),
65            Self::Validation(_) => (StatusCode::BAD_REQUEST, "CONTENTHASH_VALIDATION_ERROR"),
66            Self::Database(_) => (StatusCode::INTERNAL_SERVER_ERROR, "CONTENTHASH_DATABASE_ERROR"),
67            Self::Internal(_) => (StatusCode::INTERNAL_SERVER_ERROR, "CONTENTHASH_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 ContentHash.
85///
86/// # Routes
87///
88/// | Method | Path | Description |
89/// |--------|------|-------------|
90/// | GET | /content_hashes | List with pagination |
91/// | POST | /content_hashes | Create new |
92/// | GET | /content_hashes/:id | Get by ID |
93/// | PUT | /content_hashes/:id | Full update |
94/// | PATCH | /content_hashes/:id | Partial update |
95/// | DELETE | /content_hashes/:id | Soft delete |
96/// | POST | /content_hashes/bulk | Bulk create |
97/// | POST | /content_hashes/upsert | Upsert |
98/// | GET | /content_hashes/trash | List deleted |
99/// | POST | /content_hashes/:id/restore | Restore |
100/// | DELETE | /content_hashes/empty | Empty trash |
101/// | GET | /content_hashes/:id/deleted | Get deleted by ID |
102/// | DELETE | /content_hashes/trash/:id | Permanent delete from trash |
103/// | GET | /content_hashes/count | Count active entities |
104/// | GET | /content_hashes/trash/count | Count deleted entities |
105///
106/// # Example
107///
108/// ```text
109/// let service = Arc::new(ContentHashService::with_repository(repository));
110/// let router = create_content_hash_routes(service);
111/// ```
112pub fn create_content_hash_routes(service: Arc<ContentHashService>) -> Router {
113    BackboneCrudHandler::<ContentHashService, ContentHash, CreateContentHashDto, UpdateContentHashDto, ContentHashResponseDto>::routes(
114        service,
115        "/content_hashes",
116    )
117}
118
119/// Create Axum router with only the read (GET) endpoints for ContentHash.
120///
121/// Safe for public, unauthenticated exposure (e.g., reference data).
122/// Mutations must be served separately via `create_content_hash_write_routes`,
123/// typically wrapped in an auth middleware layer.
124pub fn create_content_hash_read_routes(service: Arc<ContentHashService>) -> Router {
125    BackboneCrudHandler::<ContentHashService, ContentHash, CreateContentHashDto, UpdateContentHashDto, ContentHashResponseDto>::read_routes(
126        service,
127        "/content_hashes",
128    )
129}
130
131/// Create Axum router with only the write (mutation) endpoints for ContentHash.
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_content_hash_write_routes(service: Arc<ContentHashService>) -> Router {
143    BackboneCrudHandler::<ContentHashService, ContentHash, CreateContentHashDto, UpdateContentHashDto, ContentHashResponseDto>::write_routes(
144        service,
145        "/content_hashes",
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_content_hash_routes<A: AuthMiddleware + Send + Sync + 'static>(
156    service: Arc<ContentHashService>,
157    auth: Arc<A>,
158) -> Router {
159    use axum::middleware;
160    use axum::response::IntoResponse;
161
162    let auth_layer = auth.clone();
163    create_content_hash_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}