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}