aboutsummaryrefslogtreecommitdiffstats
path: root/crates/turtle/src/atuin_server/router.rs
blob: d9cfc979781a931fded8db46d9ab9a4158c99a41 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
use crate::{
    atuin_common::api::{ATUIN_CARGO_VERSION, ATUIN_HEADER_VERSION, ErrorResponse},
    atuin_server::database::{db::ServerPostgres, models::User},
};
use axum::{
    Router,
    extract::{FromRequestParts, Path, Request},
    http::{self, request::Parts},
    middleware::Next,
    response::{IntoResponse, Response},
    routing::{get, post},
};
use eyre::Result;
use tower::ServiceBuilder;
use tower_http::trace::TraceLayer;
use uuid::Uuid;

use super::handlers;
use crate::atuin_server::{
    handlers::{ErrorResponseStatus, RespExt},
    metrics,
    settings::Settings,
};

pub(crate) struct UserAuth(pub(crate) User);

impl FromRequestParts<AppState> for UserAuth {
    type Rejection = ErrorResponseStatus<'static>;

    async fn from_request_parts(
        req: &mut Parts,
        state: &AppState,
    ) -> Result<Self, Self::Rejection> {
        let user_id = {
            let Path(user_id) =
                <Path<Uuid> as FromRequestParts<AppState>>::from_request_parts(req, state)
                    .await
                    .map_err(|_| {
                        ErrorResponse::reply("invalid user_id path param")
                            .with_status(http::StatusCode::BAD_REQUEST)
                    })?;

            user_id
        };

        let user = User { id: user_id };

        Ok(UserAuth(user))
    }
}

async fn teapot() -> impl IntoResponse {
    // This used to return 418: 🫖
    // Much as it was fun, it wasn't as useful or informative as it should be
    (http::StatusCode::NOT_FOUND, "404 not found")
}

/// Ensure that we only try and sync with clients on the same major version
async fn semver(request: Request, next: Next) -> Response {
    let mut response = next.run(request).await;
    response
        .headers_mut()
        .insert(ATUIN_HEADER_VERSION, ATUIN_CARGO_VERSION.parse().unwrap());

    response
}

#[derive(Clone)]
pub(crate) struct AppState {
    pub(crate) database: ServerPostgres,
    pub(crate) settings: Settings,
}

pub(crate) fn router(database: ServerPostgres, settings: Settings) -> Router {
    let routes = Router::new()
        .route("/", get(handlers::index))
        .route("/api/v0/{user_id}/record", post(handlers::v0::record::post))
        .route("/api/v0/{user_id}/record", get(handlers::v0::record::index))
        .route(
            "/api/v0/{user_id}/record/next",
            get(handlers::v0::record::next),
        );

    let path = settings.path.as_str();
    if path.is_empty() {
        routes
    } else {
        Router::new().nest(path, routes)
    }
    .fallback(teapot)
    .with_state(AppState { database, settings })
    .layer(
        ServiceBuilder::new()
            .layer(TraceLayer::new_for_http())
            .layer(axum::middleware::from_fn(metrics::track_metrics))
            .layer(axum::middleware::from_fn(semver)),
    )
}