1use 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#[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)]
48pub 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#[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}