#
mcaptcha/api/v1/
meta.rs

    1// Copyright (C) 2022  Aravinth Manivannan <realaravinth@batsense.net>
    2// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
    3//
    4// SPDX-License-Identifier: AGPL-3.0-or-later
    5
    6use actix_web::{web, HttpResponse, Responder};
    7use derive_builder::Builder;
    8use libmcaptcha::redis::{Redis, RedisConfig};
    9use serde::{Deserialize, Serialize};
    10
    11use crate::data::SystemGroup;
    12use crate::AppData;
    13use crate::{GIT_COMMIT_HASH, VERSION};
    14
    15#[derive(Clone, Debug, Deserialize, Builder, Serialize)]
    16pub struct BuildDetails {
    17    pub version: &'static str,
    18    pub git_commit_hash: &'static str,
    19}
    20
    21pub mod routes {
    22    pub struct Meta {
    23        pub build_details: &'static str,
    24        pub health: &'static str,
    25    }
    26
    27    impl Meta {
    28        pub const fn new() -> Self {
    29            Self {
    30                build_details: "/api/v1/meta/build",
    31                health: "/api/v1/meta/health",
    32            }
    33        }
    34    }
    35}
    36
    37/// emits build details of the bninary
    38#[my_codegen::get(path = "crate::V1_API_ROUTES.meta.build_details")]
    39async fn build_details() -> impl Responder {
    40    let build = BuildDetails {
    41        version: VERSION,
    42        git_commit_hash: GIT_COMMIT_HASH,
    43    };
    44    HttpResponse::Ok().json(build)
    45}
    46
    47#[derive(Clone, Debug, Deserialize, Builder, Serialize)]
    48/// Health check return datatype
    49pub struct Health {
    50    db: bool,
    51    #[serde(skip_serializing_if = "Self::is_redis")]
    52    redis: Option<bool>,
    53}
    54
    55impl Health {
    56    fn is_redis(redis: &Option<bool>) -> bool {
    57        redis.is_none()
    58    }
    59}
    60
    61/// checks all components of the system
    62#[my_codegen::get(path = "crate::V1_API_ROUTES.meta.health")]
    63async fn health(data: AppData) -> impl Responder {
    64    let mut resp_builder = HealthBuilder::default();
    65    resp_builder.redis(None);
    66
    67    resp_builder.db(data.db.ping().await);
    68
    69    if let SystemGroup::Redis(_) = data.captcha {
    70        if let Ok(r) = Redis::new(RedisConfig::Single(
    71            data.settings.redis.as_ref().unwrap().url.clone(),
    72        ))
    73        .await
    74        {
    75            let status = r.get_client().ping().await;
    76            resp_builder.redis = Some(Some(status));
    77        } else {
    78            resp_builder.redis = Some(Some(false));
    79        }
    80    };
    81
    82    HttpResponse::Ok().json(resp_builder.build().unwrap())
    83}
    84
    85pub fn services(cfg: &mut web::ServiceConfig) {
    86    cfg.service(build_details);
    87    cfg.service(health);
    88}
    89
    90#[cfg(test)]
    91pub mod tests {
    92    use actix_web::{http::StatusCode, test, App};
    93
    94    use super::*;
    95    use crate::api::v1::services;
    96    use crate::*;
    97
    98    #[actix_rt::test]
    99    async fn build_details_works() {
    100        let app = test::init_service(App::new().configure(services)).await;
    101
    102        let resp = test::call_service(
    103            &app,
    104            test::TestRequest::get()
    105                .uri(V1_API_ROUTES.meta.build_details)
    106                .to_request(),
    107        )
    108        .await;
    109        assert_eq!(resp.status(), StatusCode::OK);
    110    }
    111
    112    #[actix_rt::test]
    113    async fn health_works_pg() {
    114        let data = crate::tests::pg::get_data().await;
    115        health_works(data).await;
    116    }
    117
    118    #[actix_rt::test]
    119    async fn health_works_maria() {
    120        let data = crate::tests::maria::get_data().await;
    121        health_works(data).await;
    122    }
    123
    124    pub async fn health_works(data: ArcData) {
    125        println!("{}", V1_API_ROUTES.meta.health);
    126        let data = &data;
    127        let app = get_app!(data).await;
    128
    129        let resp = test::call_service(
    130            &app,
    131            test::TestRequest::get()
    132                .uri(V1_API_ROUTES.meta.health)
    133                .to_request(),
    134        )
    135        .await;
    136        assert_eq!(resp.status(), StatusCode::OK);
    137
    138        let health_resp: Health = test::read_body_json(resp).await;
    139        assert!(health_resp.db);
    140        assert_eq!(health_resp.redis, Some(true));
    141    }
    142}